一,JVM記憶體結構:在JVM記憶體結構中,大致(為什麼是大致?因為虛擬機器的種類繁多,有些虛擬機器把棧概念合二為一,還有其他自由的實現)會分為如下的結構:
包括本地方法棧,VM棧,程式計數器,方法區,java堆,下面逐一來看下他們的功能:
①本地方法棧:放著大量虛擬機器可直接呼叫的native方法,比如CAS模型中大量使用的Unsafe包中的方法,基本都是native方法,這些方法很多並不是用java實現的,而是C,C++等;但是可供java直接呼叫;
②,vm棧:存放執行緒執行方法時產生的棧幀,通常一個執行緒會有一個棧幀鏈,如下圖所示,一個執行緒正在執行的棧幀只會是一個當前棧幀,棧幀中包含的資料結構包括:區域性變量表(方法中的區域性變數)、運算元棧(運算過程中的中間儲存媒介)、動態連結、方法返回地址和一些額外的附加資訊,如下圖:
④,方法區:存放已經被虛擬機器載入的類資訊,常量,靜態變數,即時編譯器編譯後的程式碼等資料,還有執行時常量,通常稱為永久代,通常情況不會進行GC;
⑤,java堆:絕大多數例項物件都在此存放;
換個圖來看java記憶體模型可知:方法區和堆是執行緒共享的,其他的區域是執行緒私有的;
二,JVM GC:
1),物件是否能回收的判斷:
(1),不可達性物件可以回收;
(2),對於用可達性分析法搜尋不到的物件,GC並不一定會回收該物件。要完全回收一個物件,至少需要經過兩次標記的過程:
第一次標記:對於一個沒有其他引用的物件,篩選該物件是否有必要執行finalize()方法,如果沒有執行必要,則意味可直接回收。(篩選依據:是否複寫或執行過finalize()方法;因為finalize方法只能被執行一次)。
第二次標記:如果被篩選判定位有必要執行,則會放入FQueue佇列,並自動建立一個低優先順序的finalize執行緒來執行釋放操作。如果在一個物件釋放前被其他物件引用,則該物件會被移除FQueue佇列。
根搜尋演算法:JVM選定諸如方法區的靜態常量,本地方法中的物件等作為GC roots(物件可達樹的根節點),將建立的所有的物件引用掛在樹上,當物件引用從樹上解掛時(沒有物件再引用這個物件時),則這個物件處於不可達狀態,也即是可回收狀態;如下圖:
2)JVM記憶體分割槽和GC演算法
記憶體被切分為三塊:新生代(剛new出來的物件),老年代(從新生代GC過來的物件或者剛new出來的大物件(直接超過了新生代的空閒記憶體)),永久代(方法區資料)
新生代又被分為一塊Eden區和兩塊Survivor區;
新生代GC演算法:複製演算法採用的方式為從根集合進行掃描,將存活的物件移動到一塊空閒的區域
標記-清除:該演算法採用的方式是從跟集合開始掃描,對存活的物件進行標記,標記完畢後,再掃描整個空間中未被標記的物件,並進行清除。標記和清除的過程如下:
上圖中藍色部分是有被引用的物件,褐色部分是沒有被引用的物件。在Marking階段,需要進行全盤掃描,這個過程是比較耗時的。
清除階段清理的是沒有被引用的物件,存活的物件被保留。
標記-清除動作不需要移動物件,且僅對不存活的物件進行清理,在空間中存活物件較多的時候,效率較高,但由於只是清除,沒有重新整理,因此會造成記憶體碎片。
標記-壓縮:該演算法與標記-清除演算法類似,都是先對存活的物件進行標記,但是在清除後會把活的物件向左端空閒空間移動,然後再更新其引用物件的指標,如下圖所示
由於進行了移動規整動作,該演算法避免了標記-清除的碎片問題,但由於需要進行移動,因此成本也增加了。(該演算法適用於舊生代)
3),虛擬機器中GC的過程:
1,在初始階段,新建立的物件被分配到Eden區,survivor的兩塊空間都為空。
2,當Eden區滿了的時候,minor garbage 被觸發 。
3,經過掃描與標記,存活的物件被複制到S0,不存活的物件被回收
4,在下一次的Minor GC中,Eden區的情況和上面一致,沒有引用的物件被回收,存活的物件被複制到survivor區。然而在survivor區,S0的所有的資料都被複制到S1,需要注意的是,在上次minor GC過程中移動到S0中的兩個物件在複製到S1後其年齡要加1。此時Eden區S0區被清空,所有存活的資料都複製到了S1區,並且S1區存在著年齡不一樣的物件,過程如下圖所示:
5,再下一次MinorGC則重複這個過程,這一次survivor的兩個區對換,存活的物件被複制到S0,存活的物件年齡加1,Eden區和另一個survivor區被清空。
6,再經過幾次Minor GC之後,當存活物件的年齡達到一個閾值之後(可透過引數配置,預設是8),就會被從年輕代Promotion到老年代。
7,隨著MinorGC一次又一次的進行,不斷會有新的物件被promote到老年代。
8,上面基本上覆蓋了整個年輕代所有的回收過程。最終,MajorGC將會在老年代發生,老年代的空間將會被清除和壓縮。
4),Minor GC,Major GC,Full GC觸發條件
Minor GC:當年輕代中的Eden區滿時,觸發;
Major GC:清理老年代,通常由Minor GC觸發;
Full GC:
(1)呼叫System.gc時,建議執行full GC,但是不一定執行;
(2)老年代空間不足,
(3)方法區空間不足,
(4)透過Minor GC進入老年代的平均大小大於老年代的可用記憶體;
(5)Minor GC觸發Full GC:新生代的eden區和suvivor(使用中)向survivor(暫未使用)複製物件的時候,大於survivor(暫未使用)的記憶體,隨即把物件轉存到老年代,但同樣大於老年代的可用永存
5),JVM引數與調優:
JVM引數:
-Xmx:最大允許分配堆記憶體;
-Xms:初始分配的堆記憶體; 通常與Xmx一樣,避免每次GC後重新分配記憶體
CMSFullGCsBeforeCompaction=5:會每隔5次真正的full GC做一次壓縮
-XX:CMSInitiatingOccupancyFraction=70 是指設定CMS在對記憶體佔用率達到70%的時候開始GC(因為CMS會有浮動垃圾,所以一般都較早啟動GC);
-XX:+CMSParallelRemarkEnabled 減少第二次暫停的時間,開啟並行的remark;
-XX:+DisableExplicitGC 禁止程式碼中顯式的呼叫GC,
-XX:+DoEscapeAnalysis 開啟逃逸(例如被靜態變數引用等導致無法回收)分析
XX:+UseCMSCompactAtFullCollection 開啟碎片合併
XX:+UseConcMarkSweepG 使用CMS收集器
-XX:+UseParNewGC 年輕代為多執行緒收集。
還有更多的JAVA乾貨技術分享,敬請關注。。。
一,JVM記憶體結構:在JVM記憶體結構中,大致(為什麼是大致?因為虛擬機器的種類繁多,有些虛擬機器把棧概念合二為一,還有其他自由的實現)會分為如下的結構:
包括本地方法棧,VM棧,程式計數器,方法區,java堆,下面逐一來看下他們的功能:
①本地方法棧:放著大量虛擬機器可直接呼叫的native方法,比如CAS模型中大量使用的Unsafe包中的方法,基本都是native方法,這些方法很多並不是用java實現的,而是C,C++等;但是可供java直接呼叫;
②,vm棧:存放執行緒執行方法時產生的棧幀,通常一個執行緒會有一個棧幀鏈,如下圖所示,一個執行緒正在執行的棧幀只會是一個當前棧幀,棧幀中包含的資料結構包括:區域性變量表(方法中的區域性變數)、運算元棧(運算過程中的中間儲存媒介)、動態連結、方法返回地址和一些額外的附加資訊,如下圖:
④,方法區:存放已經被虛擬機器載入的類資訊,常量,靜態變數,即時編譯器編譯後的程式碼等資料,還有執行時常量,通常稱為永久代,通常情況不會進行GC;
⑤,java堆:絕大多數例項物件都在此存放;
換個圖來看java記憶體模型可知:方法區和堆是執行緒共享的,其他的區域是執行緒私有的;
二,JVM GC:
1),物件是否能回收的判斷:
(1),不可達性物件可以回收;
(2),對於用可達性分析法搜尋不到的物件,GC並不一定會回收該物件。要完全回收一個物件,至少需要經過兩次標記的過程:
第一次標記:對於一個沒有其他引用的物件,篩選該物件是否有必要執行finalize()方法,如果沒有執行必要,則意味可直接回收。(篩選依據:是否複寫或執行過finalize()方法;因為finalize方法只能被執行一次)。
第二次標記:如果被篩選判定位有必要執行,則會放入FQueue佇列,並自動建立一個低優先順序的finalize執行緒來執行釋放操作。如果在一個物件釋放前被其他物件引用,則該物件會被移除FQueue佇列。
根搜尋演算法:JVM選定諸如方法區的靜態常量,本地方法中的物件等作為GC roots(物件可達樹的根節點),將建立的所有的物件引用掛在樹上,當物件引用從樹上解掛時(沒有物件再引用這個物件時),則這個物件處於不可達狀態,也即是可回收狀態;如下圖:
2)JVM記憶體分割槽和GC演算法
記憶體被切分為三塊:新生代(剛new出來的物件),老年代(從新生代GC過來的物件或者剛new出來的大物件(直接超過了新生代的空閒記憶體)),永久代(方法區資料)
新生代又被分為一塊Eden區和兩塊Survivor區;
新生代GC演算法:複製演算法採用的方式為從根集合進行掃描,將存活的物件移動到一塊空閒的區域
標記-清除:該演算法採用的方式是從跟集合開始掃描,對存活的物件進行標記,標記完畢後,再掃描整個空間中未被標記的物件,並進行清除。標記和清除的過程如下:
上圖中藍色部分是有被引用的物件,褐色部分是沒有被引用的物件。在Marking階段,需要進行全盤掃描,這個過程是比較耗時的。
清除階段清理的是沒有被引用的物件,存活的物件被保留。
標記-清除動作不需要移動物件,且僅對不存活的物件進行清理,在空間中存活物件較多的時候,效率較高,但由於只是清除,沒有重新整理,因此會造成記憶體碎片。
標記-壓縮:該演算法與標記-清除演算法類似,都是先對存活的物件進行標記,但是在清除後會把活的物件向左端空閒空間移動,然後再更新其引用物件的指標,如下圖所示
由於進行了移動規整動作,該演算法避免了標記-清除的碎片問題,但由於需要進行移動,因此成本也增加了。(該演算法適用於舊生代)
3),虛擬機器中GC的過程:
1,在初始階段,新建立的物件被分配到Eden區,survivor的兩塊空間都為空。
2,當Eden區滿了的時候,minor garbage 被觸發 。
3,經過掃描與標記,存活的物件被複制到S0,不存活的物件被回收
4,在下一次的Minor GC中,Eden區的情況和上面一致,沒有引用的物件被回收,存活的物件被複制到survivor區。然而在survivor區,S0的所有的資料都被複制到S1,需要注意的是,在上次minor GC過程中移動到S0中的兩個物件在複製到S1後其年齡要加1。此時Eden區S0區被清空,所有存活的資料都複製到了S1區,並且S1區存在著年齡不一樣的物件,過程如下圖所示:
5,再下一次MinorGC則重複這個過程,這一次survivor的兩個區對換,存活的物件被複制到S0,存活的物件年齡加1,Eden區和另一個survivor區被清空。
6,再經過幾次Minor GC之後,當存活物件的年齡達到一個閾值之後(可透過引數配置,預設是8),就會被從年輕代Promotion到老年代。
7,隨著MinorGC一次又一次的進行,不斷會有新的物件被promote到老年代。
8,上面基本上覆蓋了整個年輕代所有的回收過程。最終,MajorGC將會在老年代發生,老年代的空間將會被清除和壓縮。
4),Minor GC,Major GC,Full GC觸發條件
Minor GC:當年輕代中的Eden區滿時,觸發;
Major GC:清理老年代,通常由Minor GC觸發;
Full GC:
(1)呼叫System.gc時,建議執行full GC,但是不一定執行;
(2)老年代空間不足,
(3)方法區空間不足,
(4)透過Minor GC進入老年代的平均大小大於老年代的可用記憶體;
(5)Minor GC觸發Full GC:新生代的eden區和suvivor(使用中)向survivor(暫未使用)複製物件的時候,大於survivor(暫未使用)的記憶體,隨即把物件轉存到老年代,但同樣大於老年代的可用永存
5),JVM引數與調優:
JVM引數:
-Xmx:最大允許分配堆記憶體;
-Xms:初始分配的堆記憶體; 通常與Xmx一樣,避免每次GC後重新分配記憶體
CMSFullGCsBeforeCompaction=5:會每隔5次真正的full GC做一次壓縮
-XX:CMSInitiatingOccupancyFraction=70 是指設定CMS在對記憶體佔用率達到70%的時候開始GC(因為CMS會有浮動垃圾,所以一般都較早啟動GC);
-XX:+CMSParallelRemarkEnabled 減少第二次暫停的時間,開啟並行的remark;
-XX:+DisableExplicitGC 禁止程式碼中顯式的呼叫GC,
-XX:+DoEscapeAnalysis 開啟逃逸(例如被靜態變數引用等導致無法回收)分析
XX:+UseCMSCompactAtFullCollection 開啟碎片合併
XX:+UseConcMarkSweepG 使用CMS收集器
-XX:+UseParNewGC 年輕代為多執行緒收集。
還有更多的JAVA乾貨技術分享,敬請關注。。。