u 程序計數器html
u Java棧(虛擬機棧)java
u 本地方法棧算法
u Java堆編程
u 方法區及其運行時常量池數組
u 新生代和老年代安全
u 參數設置數據結構
u 垃圾回收(Minor GC 和 Full GC)和回收算法多線程
u finalize()、減小GC開銷、觸發主GC的條件jvm
u 判斷無用對象、四種引用方式、爲何進行垃圾回收ui
u String、StringBuffer與StringBuilder
u 類加載機制
第1、程序計數器(PC)
程序計數器(Program Counter Register)是一塊較小的內存空間,它能夠看作當前線程所執行的字節碼的行號指示器,字節碼解釋器工做時就是經過改變這個計數器的值來取下一條須要執行的字節碼指令
因爲Java 虛擬機的多線程是經過線程輪流切換並分配處理器執行時間的方式來實現的,在任何一個肯定的時刻,一個處理器(對於多核處理器來講是一個內核)只會執行一條線程中的指令。所以,爲了線程切換後能恢復到正確的執行位置,每條線程都須要有一個獨立的程序計數器,各條線程之間的計數器互不影響,獨立存儲,咱們稱這類內存區域爲「線程私有」的內存。
若是線程正在執行的是一個Java 方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;若是正在執行的是Natvie 方法,這個計數器值則爲空(Undefined)。
注:程序計數器是線程私有的,每條線程都會有一個獨立的程序計數器
第2、Java棧(虛擬機棧)
Java棧就是Java中的方法執行的內存模型,每一個方法在執行的同時都會建立一個棧幀(關於棧幀後面介紹),這個棧幀用於存儲局部變量表、操做數棧、動態連接、方法出口等信息,每一個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。
注:Java棧也是線程私有的。
異常可能性:對於棧有兩種異常狀況:若是線程請求的棧深度大於棧所容許的深度,將拋出StackOverflowError異常,若是虛擬機棧能夠動態拓展,在拓展的時沒法申請到足夠的內存,將會拋出OutOfMemoryError異常
棧幀
1) 局部變量表
2) 操做數棧
3) 整數加法
4) 法返回地址
第3、本地方法棧
本地方法棧與Java棧所發揮的做用是很是類似的,它們之間的區別不過是Java棧執行Java方法,本地方法棧執行的是本地方法。
注:本地方法棧也是線程私有的
異常可能性:和Java棧同樣,可能拋出StackOverflowError和OutOfMemeryError異常
第4、Java堆
對於大多數應用來講,Java堆是Java虛擬機所管理的內存中最大的一塊,在虛擬機啓動時建立。此內存區域的惟一目的就是存放對象實例,幾乎全部的對象實例都在這裏分配內存,固然咱們後面說到的垃圾回收器的內容的時候,其實Java堆就是垃圾回收器管理的主要區域。
注:堆是線程共享的
異常可能性:若是堆中沒有內存完成實例分配,而且堆也沒法再拓展時,將會拋出OutOfMemeryError異常
第5、方法區(若是一個系統不斷地產生新的類,而沒有回收,那最終很是有可能致使永久區溢出。)
方法區它用於存儲已被虛擬機加載的類信息(類型信息,字段信息,方法信息,其餘信息)靜態量、即時編譯器編譯後的代碼等數據。方法區是線程安全的
注:方法區和堆同樣是線程共享的
異常可能性:當方法區沒法知足內存分配需求時,將拋出OutOfMemeryError異常
運行時常量池
用於存放編譯器生成的各類字面量和符號引用當常量池沒法再申請到內存時就會拋出OutOfMemoryError異常。
問:在方法裏面建立的本地對象,它會建立在內存結構的哪一個地方?如何訪問該對象?
在Java棧的棧幀裏面建立了對象的引用,在堆上建立了對象,棧幀裏的引用指向堆中的對象。
參數配置
-Xss是設置棧的
Java的垃圾回收機制是Java虛擬機提供的能力,用於在空閒時間以不定時的方式動態回收無任何引用的對象佔據的內存空間。
堆的內存分配
新生代(Young Generation)(默認的Eden:Survivor = 8:1)
1.全部新生成的對象首先都是放在新生代的。新生代的目標就是儘量快速的收集掉那些生命週期短的對象。
2.新生代內存按照8:1:1的比例分爲一個eden區和兩個survivor(survivor0,survivor1)區。一個Eden區,兩個 Survivor區(通常而言)。大部分對象在Eden區中生成。回收時先將eden區存活對象複製到一個survivor0區,而後清空eden區,當這個survivor0區也存放滿了時,則將eden區和survivor0區存活對象複製到另外一個survivor1區,而後清空eden和這個survivor0區,此時survivor0區是空的,而後將survivor0區和survivor1區交換,即保持survivor1區爲空, 如此往復。
3.當survivor1區不足以存放 eden和survivor0的存活對象時,就將存活對象直接存放到老年代。如果老年代也滿了就會觸發一次Full GC,也就是新生代、老年代都進行回收
4.新生代發生的GC也叫作Minor GC,MinorGC發生頻率比較高(不必定等Eden區滿了才觸發)。通常把老年帶發生的gc叫full GC
在Minor GC時會將新生代中還存活着的對象複製進一個Survivor中,而後對Eden和另外一個Survivor進行清理。因此,日常可用的新生代大小爲Eden的大小+一個Survivor的大小。全部的Minor GC都會觸發全世界的暫停(stop-the-world除了垃圾收集收集器線程以外的線程都被掛起),中止應用程序的線程,不過這個過程很是短暫。Eden 和Survivor區不存在內存碎片。
當對象在 Eden出生後,在通過一次 Minor GC 後,若是對象還存活,而且可以被另一塊 Survivor 區域所容納( 上面已經假設爲 from 區域,這裏應爲 to 區域,即 to 區域有足夠的內存空間來存儲 Eden 和 from 區域中存活的對象 ),則使用複製算法將這些仍然還存活的對象複製到另一塊 Survivor 區域 ( 即 to 區域 ) 中,而後清理所使用過的 Eden 以及 Survivor 區域 ( 即from 區域 ),而且將這些對象的年齡設置爲1,之後對象在 Survivor 區每熬過一次 Minor GC,就將對象的年齡 + 1,當對象的年齡達到某個值時 ( 默認是 15 歲,能夠經過參數 -XX:MaxTenuringThreshold 來設定),這些對象就會成爲老年代。
對於一些較大的對象 ( 即須要分配一塊較大的連續內存空間 ) 則是直接進入到老年代。
在執行機制上JVM提供了串行GC(SerialGC)、並行回收GC(ParallelScavenge)和並行GC(ParNew)
年老代(Old Generation)
1 . 在年輕代中經歷了N次垃圾回收後仍然存活的對象,就會被放到年老代中。所以,能夠認爲年老代中存放的都是一些生命週期較長的對象。
2 . 內存比新生代也大不少(大概比例是1:2),當老年代內存滿時觸發Full GC 發生頻率比較低,老年代對象存活時間比較長,存活率標記高。
什麼狀況下,新生代的對象會進入老年代呢?
當Minor GC時,新生代存活的對象大於Survivor的大小時,這時一個Survivor裝不下它們,那麼它們就會進入老年代。
在新生代的每一次Minor GC 都會給在新生代中的對象+1歲,默認到15歲時就會重新生代進入老年代-XX:MaxTenuringThreshold來設置這個臨界點。
若是設置了-XX:PretenureSizeThreshold3M 那麼大於3M的對象就會直接就進入老年代。
3.finalize()方法何時被調用?
l 垃圾回收器(garbage colector)決定回收某對象時,就會運行該對象的finalize()方法 可是在Java中很不幸,若是內存老是充足的,那麼垃圾回收可能永遠不會進行,也就是說filalize()可能永遠不被執行,顯然期望它作收尾工做是靠不住的。 那麼finalize()到底是作什麼的呢?
l 它最主要的用途是回收特殊渠道申請的內存。因爲垃圾回收器只知道那些顯示地經由new分配的內存空間,因此它不知道該如何釋放這塊「特殊」的內存區域,那麼這個時候java容許在類中定義一個由 finalize()方法
問:.減小GC開銷的措施
(1)不要顯式調用System.gc()
(2)儘可能減小臨時對象的使用
(3)對象不用時最好顯式置爲Null
(4)儘可能使用StringBuffer,而不用String來累加字符串
問:.何時會進行GC(Garbage Collector)
(1)當應用程序空閒時,即沒有應用線程在運行時,GC會被調用。由於GC在優先級最低的線程中進行,因此當應用忙時,GC線程就不會被調用,但如下條件除外。
(2)主動調用system.gc()
(2)eden區滿時、老年代滿時
問:下降GC的調優
可調試NewSize、permSize、NewRatio、SurvivorRatio、進入老年帶的臨界歲數
Java對象在內存中的狀態:
可達的/可觸及的:
Java對象被建立後,若是被一個或多個變量引用,那就是可達的。即從根節點能夠觸及到這個對象。
其實就是從根節點掃描,只要這個對象在引用鏈中,那就是可觸及的。
可恢復的:
Java對象再也不被任何變量引用就進入了可恢復狀態。
在回收該對象以前,該對象的finalize()方法進行資源清理。若是在finalize()方法中從新讓變量引用該對象,則該對象再次變爲可達狀態,不然該對象進入不可達狀態
不可達的:
Java對象不被任何變量引用,且系統在調用對象的finalize()方法後依然沒有使該對象變成可達狀態(該對象依然沒有被變量引用),那麼該對象將變成不可達狀態。
當Java對象處於不可達狀態時,系統纔會真正回收該對象所佔有的資源。
兩種最基本的垃圾收集器
一、Serial收集器:(串行收集器)
這個收集器是一個單線程的收集器,但它的單線程的意義並不只僅說明它只會使用一個CPU或一條收集線程去完成垃圾收集工做,更重要的是在它進行垃圾收集時,必須暫停其餘全部的工做線程(Stop-The-World:將用戶正常工做的線程所有暫停掉),直到它收集結束。收集器的運行過程以下圖所示:
二、ParNew收集器:Serial收集器的多線程版本(使用多條線程進行GC)
ParNew收集器是Serial收集器的多線程版本。
引用計數法
給對象中添加一個引用計數器,任什麼時候刻計數器爲0的對象就是不可能再被使用的。主流的java虛擬機並無選用引用計數算法來管理內存,其中最主要的緣由是:它很難解決對象之間相互循環引用的問題。
可達性分析法(經常使用)
該方法的基本思想是經過一系列的根對象的集合做爲起點進行搜索,搜索所通過的路徑稱爲「引用鏈」,從這些根對象開始,任何能夠被觸及的對象都是被認爲是「活動」的對象。沒法被觸及的對象被認爲是垃圾,由於它們不在影響程序的將來執行。在後面介紹標記-清理算法/標記整理算法時,也會一直強調從根節點開始,對全部可達對象作一次標記。
可做爲GC Roots的對象包括下面幾種:
1.Mark-Sweep(標記-清除)算法
這是最基礎的垃圾回收算法,之因此說它是最基礎的是由於它最容易實現,思想也是最簡單的。標記-清除算法分爲兩個階段:標記階段和清除階段。標記階段的任務是標記出全部須要被回收的對象,清除階段就是回收被標記的對象所佔用的空間。
從圖中能夠很容易看出標記-清除算法實現起來比較容易,可是有一個比較嚴重的問題就是容易產生內存碎片,碎片太多可能會致使後續過程當中須要爲大對象分配空間時沒法找到足夠的空間而提早觸發新的一次垃圾收集動做。
2.Copying(複製)算法
爲了解決Mark-Sweep算法的缺陷,Copying算法就被提了出來。它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用的內存空間一次清理掉,這樣一來就不容易出現內存碎片的問題。問題是佔用內存較多
3.Mark-Compact(標記-整理)算法
爲了解決Copying算法的缺陷,充分利用內存空間,提出了Mark-Compact算法。該算法標記階段和Mark-Sweep同樣,可是在完成標記以後,它不是直接清理可回收對象,而是將存活對象都向一端移動,而後清理掉端邊界之外的內存。
4.分代收集算法
通常是把Java堆分爲新生代和老年代,這樣就能夠根據各個年代的特色採用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,那就選用複製算法,只須要付出少許存活對象的複製成本就能夠完成收集。而老年代中由於對象存活率高、沒有額外空間對它進行分配擔保,就必須使用「標記-清理」或「標記-整理」算法來進行回收。方法區永久代,回收方法同老年代。
老年代採用的是標記-清除或者標記-整理算法,這兩個算法主要看虛擬機採用的哪一個收集器,兩種算法的區別是:標記-清除可能會產生大量連續的內存碎片。
String 字符串常亮,底層是數組實現的
StringBuilder:線程非安全的
StringBuffer:線程安全的
對於三者使用的總結:
1.若是要操做少許的數據用 = String
2.單線程操做字符串緩衝區下操做大量數據 = StringBuilder
3.多線程操做字符串緩衝區下操做大量數據 = StringBuffer
四.爲何要進行垃圾回收
在C++中,對象所佔的內存在程序結束運行以前一直被佔用,在明確釋放以前不能分配給其它對象;而在Java中,當沒有對象引用指向原先分配給某個對象的內存時,該內存便成爲垃圾。 JVM的一個系統級線程會自動釋放該內存塊,減輕編程的負擔。事實上,除了釋放沒用的對象,垃圾回收也能夠清除內存記錄碎片。
五.java虛擬機類加載機制
包括加載、連接(含驗證、準備、解析)、初始化
其中類加載的過程包括了加載、驗證、準備、解析、初始化五個階段。
l 加載
加載時類加載過程的第一個階段,在加載階段,虛擬機須要完成如下三件事情:
一、經過一個類的全限定名來獲取其定義的二進制字節流。
二、將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構。
三、在Java堆中生成一個表明這個類的java.lang.Class對象,做爲對方法區中這些數據的訪問入口。
對於任意一個類,都須要由它的類加載器和這個類自己一同肯定其在就Java虛擬機中的惟一性
類加載器:啓動類加載器、擴展類加載器、應用程序類加載器、自定義類加載器
雙親委派模型:若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試加載這個類,而是把請求委託給父加載器去完成,依次向上,所以,全部的類加載請求最終都應該被傳遞到頂層的啓動類加載器中,只有當父加載器在它的搜索範圍中沒有找到所需的類時,即沒法完成該加載,子加載器纔會嘗試本身去加載該類。(Java中的Object類,不管哪個類加載器要加載這個類,最終都是委派給處於模型最頂端的啓動類加載器進行加載,所以Object在各類類加載環境中都是同一個類。)
l 驗證
驗證的目的是爲了確保Class文件中的字節流包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。
l 準備
準備階段是正式爲類變量分配內存並設置類變量初始值(零)的階段,這些內存都將在方法區中分配。
l 解析
解析階段是虛擬機將常量池中的符號引用轉化爲直接引用的過程。
l 初始化
初始化是類加載過程的最後一步,此階段纔開始真正執行類中定義的Java程序代碼(靜態語句塊和構造器)
類的初始化過程(重要)
Student s = new Student();在內存中作了哪些事情?
強引用、軟引用、弱引用、虛引用
l 強引用:當咱們使用new 這個關鍵字建立對象時被建立的對象就是強引用,垃圾回收器就不會去回收有強引用的對象。當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具備強引用的對象來解決內存不足的問題。
l 軟引用:若是一個對象具有軟引用,若是內存空間足夠,那麼垃圾回收器就不會回收它,若是內存空間不足了,就會回收該對象。固然沒有被回收以前,該對象依然能夠被程序調用。java.lang.ref.SoftReference
l 弱引用:若是一個對象只具備弱引用,只要垃圾回收器在本身的內存空間中線程檢測到了,就會當即被回收,對應內存也會被釋放掉。java.lang.ref.WeakReference
l 虛引用:若是一個對象只具備虛引用,那麼它就和沒有任何引用同樣,隨時會被jvm看成垃圾進行回收。虛引用目的:當對象被收集器回收時收到系統通知。java.lang.ref.PhantomReference