這裏以Java8爲例java
首先咱們給出以下代碼用來觸發GCbash
public static void main(String[] args) {
// 每100毫秒建立100線程,每一個線程建立一個1M的對象,即每100ms申請100M堆空間
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
try {
// 申請1M
byte[] temp = new byte[1024 * 1024];
Thread.sleep(new Random().nextInt(1000)); // 隨機睡眠1秒之內
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}, 1000, 100, TimeUnit.MILLISECONDS);
}
複製代碼
咱們要模擬的場景是年輕代不斷地Young GC,並有一部分對象晉升到老年代,當老年代空間不足時觸發Full GC。dom
程序邏輯:每100毫秒建立100個線程,每一個線程建立一個1M的對象,即每100ms申請100M堆空間。之因此每一個線程隨機睡眠1s,是爲了不對象朝生夕滅,保證能夠有一部分對象能晉升到老年代,更好的觸發Young GC 和 Full GC,注意這個睡眠時間若是大了,會致使OOM,若是小了,很難觸發FULL GC。工具
啓動Java進程:java -Xms200m -Xmx200m -Xmn100m -verbose:gc -XX:+PrintGCDetails -Xloggc:./gc.log -XX:+PrintGCDateStamps -jar demo-0.0.1-SNAPSHOT.jar性能
-Xms200m -Xmx200m 最小/最大堆內存 200Mspa
-Xmn100m 年輕代內存 100M線程
-verbose:gc 開啓GC日誌日誌
-XX:+PrintGCDetails -Xloggc:./gc.log -XX:+PrintGCDateStamps 將GC日誌詳情輸入到gc.log中code
jcmd 獲取咱們Java進程的Id:6264cdn
jmap -heap 6264查看堆信息
第一次查看,咱們發現 Eden區是98M,S0、S1是1M
第二次查看, Eden區是99M,S0、S1是0.5M
Eden區與Survivor區的比例在動態的變化,並非默認的8:1:1。
原來咱們使用默認的垃圾收集器Parallel Scavenge+Parallel Old組合,而該收集器下-XX:+UseAdaptiveSizePolicy是默認開啓的,即Eden區與Survivor區比例根據GC狀況會自適應變化。
咱們加上參數,關閉年輕代自適應,年輕代比例設置爲8:1:1
-XX:-UseAdaptiveSizePolicy -XX:SurvivorRatio=8
另外爲了儘早的觸發FULL GC,咱們新增虛擬機參數
-XX:MaxTenuringThreshold=10
晉升年齡由默認的15修改成10,使得年輕代的對象更容易晉升到老年代
重啓虛擬機查看jmap
Eden區80M 已使用51M,當前使用率63.8%
S0區10M 已使用0.43M,使用率4.37%
S1區10M 使用率爲空
查看咱們輸出的GC日誌gc.log,選取其中兩段
2019-06-09T02:55:30.993+0800: 330.811: [GC (Allocation Failure) [PSYoungGen: 82004K->384K(92160K)] 184303K->102715K(194560K), 0.0035647 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 2019-06-09T02:55:30.997+0800: 330.815: [Full GC (Ergonomics) [PSYoungGen: 384K->0K(92160K)] [ParOldGen: 102331K->5368K(102400K)] 102715K->5368K(194560K), [Metaspace: 16941K->16914K(1064960K)], 0.0213953 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
[GC (Allocation Failure) [PSYoungGen: 82004K->384K(92160K)] 184303K->102715K(194560K), 0.0035647 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
解釋:
年輕代GC:[GC前年輕代80.08M->GC後0.37M(年輕代總大小90M)]GC前堆179.98M->GC後堆100.3M(堆總大小190M),用時]
其中年輕代總大小是90M而不是100M,這裏我理解是年輕代當前最大申請到90M
100M*80%=80M 是Eden區大小
80M*80% = 64M Eden區默認佔用超過8成即64M就會觸發YoungGC
[Full GC (Ergonomics) [PSYoungGen: 384K->0K(92160K)] [ParOldGen: 102331K->5368K(102400K)] 102715K->5368K(194560K), [Metaspace: 16941K->16914K(1064960K)], 0.0213953 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
解釋:
[GC前年輕代0.375M->GC後年輕代0M(年輕代總大小90M)][GC前老年代99.93M->GC後老年代5.24M(老年代總大小100M)]GC前堆100.3M->GC後堆5.24M(堆總大小190M),[元數據區:GC前16.5,GC後16.5(元數據區總大小1040M)],用時]
能夠推測出這次FullGC緣由是年輕代晉升老年代空間不足致使
這裏咱們利用 gceasy.io/ 分析一下
(1)統計年輕代、老年代、元數據區最大可用空間以及峯值,這裏元數據區大小在咱們的虛擬機參數沒有配置,因此取的是默認值1040M
(2)吞吐量、GC平均延遲、最大延遲以及延遲區間的統計
(3)堆所用大小的實時分析,紅色位置是發生了FullGC使得堆總量直線降低
(4)GC空間總量和時間的統計
(5)各種GC時間、GC次數、GC總量等指標
GC日誌分析能夠幫助咱們宏觀的監控GC運行狀況。一方面若是頻繁的FullGC會有嚴重的性能問題(STW),另外一方面過於頻繁的GC,即GC佔用系統正常運行的比重過多,吞吐量低,則是必定程度上的性能資源浪費。若系統存在性能問題,根據GC分析各項指標的做爲參考,咱們也能夠適當的在程序裏或虛擬機參數作些調優。