1. 程式人生 > >GC收集器介紹及GC日誌閱讀

GC收集器介紹及GC日誌閱讀

GC收集器:

(1)Serial收集器(新生代收集器)

單執行緒收集垃圾,在其回收垃圾的時候必須暫停其他所有工作程序

(2)ParNew收集器(新生代收集器)

Serial收集器的多執行緒版本,除了Serial收集器之外,目前只有它能和CMS收集器配合工作

(3)Parallel Scavenge收集器(新生代收集器)

吞吐量優先收集器,吞吐量 = 執行使用者程式碼時間 / (執行使用者程式碼時間+垃圾收集時間)

自適應調節引數設定開關: -XX: +UseAdaptiveSizePolicy

控制最大垃圾收集停頓時間:-XX:MaxGCPauseMillis

設定吞吐量大小:-XX:GCTimeRatio(預設值為99,即允許最大1/(1+99)的垃圾收集時間)

(4)Serial Old收集器(老年代收集器)

Serial收集器的老年代版本,同樣是一個單執行緒收集器,兩種用途:在jdk1.5版本及以前版本與Parallel Scavenge收集器組合使用;作為CMS收集器的後備方案

(5)Parallel Old收集器(老年代收集器)

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,jdk1.6開始提供,在注重吞吐量及CPU資源敏感的場合,都可以優先考慮Parallel Scavenge+Parallel Old的組合

(6)CMS收集器(老年代併發收集器)

CMS收集器是基於“標記-清除”演算法實現的,過程分四步:初始標記——>併發標記——>重新標記——>併發清除;初始標記和重新標記仍需STW,耗時最長的併發標記和併發清除可以與使用者執行緒併發執行。

CMS收集器有三個明顯的缺點:

①CMS收集器對CPU資源非常敏感,在併發階段,它雖然不會導致使用者執行緒停頓,但是會因為佔用了一部分CPU資源而導致應用程式變慢,總吞吐量會降低.

②CMS收集器無法收集浮動垃圾(在併發過程中使用者執行緒新產生的垃圾),可能會出現“Concurrent Mode Failure”失敗而導致另一次Full GC的產生;由於老年代沒有分配擔保機制,需要預留一部分記憶體作為併發收集時的程式使用,如果預留的記憶體無法滿足程式需要,將會啟動後備預案:臨時啟動Serial Old收集器對老年代重新進行垃圾收集,這樣導致的停頓時間就會變得很長了。

③CMS收集器會導致空間碎片過多提前觸發一次Full GC,因為它是基於“標記-清除”演算法實現的。

(7)G1收集器(老年代併發收集器)

G1收集器是一款面向服務端應用的垃圾收集器,與其他的GC收集器相比,GC收集器具備以下特點:

①並行與併發

②分代收集

③空間整合

④可預測的停頓

 

————————————————————————————————————————————

記憶體與回收策略:

 

3.6.1物件優先在Eden區分配記憶體

測試程式碼:

public class test0305 {

 

private static final int _1MB = 1024 * 1024;

/**

* VM 引數:-verbose:gc -Xms20M -Xms20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

* 物件優先在Eden分配

*/

public static void main(String[] args) {

byte[] allocation1,allocation2,allocation3,allocation4;

allocation1 = new byte[2 * _1MB];

allocation2 = new byte[2 * _1MB];

allocation3 = new byte[2 * _1MB];

allocation4 = new byte[4 * _1MB];//出現一次Minor GC

System.out.println("hello");

}

}

 

列印GC結果:

 

引數說明:

設定虛擬機器的堆記憶體為20M不可擴容,其中新生代記憶體為10M,Eden塊:Survivor塊=8:1,列印GC日誌,GC收集器採用Serial+Serial old組合

GC結果閱讀:

Eden塊已使用記憶體由7292k變為625k(總大小9216k),堆記憶體由7292K變為6769K(總大小19456k)

Eden塊記憶體大小 8192k 已使用52%

Survivor塊1大小為1024k 已使用61%

Survivor塊2大小為1024k 已使用0%

老年代記憶體大小為10240k,已使用60%

GC結果分析:

Eden塊記憶體被GC收集時,allocation1,allocation2,allocation3三個物件轉移到了老年代,allocation4被放入到Eden塊,GC收集前後堆記憶體減小了600k的原因是 當前堆記憶體 = Eden+Survivor2+老年代,減小的記憶體跑到了Survivor1裡,沒有被計算進堆記憶體。

 

———————————————————————————————————————————————————

 

3.6.2大物件直接進入老年代

測試程式碼:

 

public class test0306 {

 

private static final int _1MB = 1024 * 1024;

/**

* VM 引數:-verbose:gc -Xms20M -Xms20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

* -XX:pretenureSizeThreshold=3145728

*/

public static void main(String[] args) {

byte[] allocation;

allocation = new byte[4 * _1MB];

System.out.println("hi");

}

 

}

測試結果:

引數說明:

-XX:pretenureSizeThreshold=3145728,當物件大於3M時直接進入老年代;

GC結果閱讀:

新生代記憶體大小9216k,已使用1312k

Eden區記憶體大小8192k,已使用16%

Survivor區1記憶體大小1024k,已使用0%

Survivor區2記憶體大小1024k,已使用0%

老年代記憶體大小10240k,已使用40%

GC結果分析:

allocation陣列物件大小為4M,大於3M,直接進入老年代,佔比40%。

_________________________________________________________________________________________________

3.7.1長期存活的物件直接進入老年代

測試程式碼:

 

public class test0307 {

 

private static final int _1MB = 1024 * 1024;

/**

* VM 引數:-verbose:gc -Xms20M -Xms20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1

* -XX:+PrintTenuringDistribution

*/

public static void main(String[] args) {

byte[] allocation1,allocation2,allocation3;

allocation1 = new byte[_1MB/4];

//什麼時候進入到老年代取決於MaxTenuringThreshold設定

allocation2 = new byte[4 * _1MB];

allocation3 = new byte[4 * _1MB];

allocation3 = null;

allocation3 = new byte[4 * _1MB];

System.out.println("hi");

}

 

}

測試結果1(設定年齡為1時進入老年代):

引數說明:

-XX:MaxTenuringThreshold=1 設定物件年齡為1時進入到老年代 -XX:+PrintTenuringDistribution 顯示物件的年齡

GC結果閱讀:

Survivor區大小的一半是524288bytes,Survivor容納的物件年齡為1時轉移到老年代

第一次回收:新生代記憶體大小5500k變成881k(總大小9216k),堆記憶體大小5500k變成4977k(總大小19456k)

第二次回收:新生代記憶體大小4977k變成0k(總大小9216k),堆記憶體大小9073k變成4976k(總大小19456k)

新生代總記憶體大小9216k,已使用4337k

Eden塊記憶體大小8192k,已使用52%

Survivor1記憶體大小1024k,已使用0%

Survivor2記憶體大小為1024k,已使用0%

老年代記憶體大小10250k,已使用48%

GC結果分析:

第一次GC發生在allocation3物件分配記憶體的時候,此時Eden塊8M記憶體(已分配1/4M+4M)不足以再分配足夠記憶體給allocation3,Survivor區可以容納下allocation1物件,複製到Survivor2區,allocation2物件通過分配擔保機制轉移到到老年代,allocation3物件進入到Eden塊。

第二次GC發生在第二次給allocation3分配記憶體的時候,因為allocation3=null的語句,導致之前的4M記憶體失去了引用(可被回收,但沒觸發GC),因為Eden區存在初始時記憶體佔用幾百k的原因,不足以再分配一個4M記憶體給allocation3(Eden塊已分配幾百k+4M),觸發第二次GC,將Survivor中的allocation1和失效的allocation3的4M記憶體回收,最後為allocation3分配新的4M記憶體

 

測試結果2(設定年齡為15時進入老年代):

參照《深入理解Java虛擬機器》P96結果(jdk1.7之後對虛擬機器進行了改動。jdk1.8版本實驗結果有誤差)

具體詳情解釋見:https://www.cnblogs.com/zyh186/p/7349707.html

GC結果閱讀:

Survivor區大小的一半是524288bytes,Survivor容納的物件年齡為1時轉移到老年代

第一次回收:新生代記憶體大小4859k變成404k(總大小9216k),堆記憶體大小4859k變成4500k(總大小19456k)

第二次回收:新生代記憶體大小4500k變成404k(總大小9216k),堆記憶體大小8596k變成4500k(總大小19456k)

新生代總記憶體大小9216k,已使用4582k

Eden塊記憶體大小8192k,已使用51%

Survivor1記憶體大小1024k,已使用39%

Survivor2記憶體大小為1024k,已使用0%

老年代記憶體大小10250k,已使用40%

GC結果分析:

第一次GC和測試1一致,第二次GC在沒有足夠記憶體分配給allocation3時,沒有轉移Survivor塊中的allocation1物件(因為此時allocation1的年齡為1,不滿足轉移到老年代條件),釋放了Eden塊無效的4M記憶體,重新分配了4M記憶體給allocation3