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