JVM中的ExplicitGCInvokesConcurrent選項

問題描述

最近常收到機器load過高的報警,時間不定,機器也不定,特意花時間分析了一下這個問題。

首先通過tsar -q命令瞭解系統整體情況,結果如下:


可以看出,1點50分load達到6.74,首先懷疑是不是在進行full gc,於是查看gc 日誌:

2014-08-06T13:52:59.104+0800: 79241.456: [Full GC (System) 79241.456: [CMS: 453193K->405395K(2682880K), 1.8669940 secs] 989281K->405395K(4986880K), [CMS Perm : 130171K->130107K(212036K)], 1.8672510 secs] [Times: user=1.86 sys=0.01, real=1.87 secs] 
果然進行了一次Full gc,再次查看了整個gc日誌,發現每隔1小時都進行一次Full gc,
2014-08-06T09:52:51.820+0800: 64834.172: [Full GC (System) 64834.172: [CMS: 429497K->398846K(2682880K), 1.7223380 secs] 525544K->398846K(4986880K), [CMS Perm : 126982K->126958K(211460K)], 1.7226100 secs] [Times: user=1.73 sys=0.00, real=1.72 secs] 
2014-08-06T10:52:53.543+0800: 68435.895: [Full GC (System) 68435.895: [CMS: 623928K->404290K(2682880K), 1.8826690 secs] 735217K->404290K(4986880K), [CMS Perm : 127085K->127029K(211652K)], 1.8828980 secs] [Times: user=1.87 sys=0.01, real=1.89 secs] 
2014-08-06T11:52:55.428+0800: 72037.779: [Full GC (System) 72037.780: [CMS: 451171K->400673K(2682880K), 1.7490430 secs] 617591K->400673K(4986880K), [CMS Perm : 127157K->127104K(211844K)], 1.7492450 secs] [Times: user=1.76 sys=0.00, real=1.75 secs] 
2014-08-06T12:52:57.178+0800: 75639.530: [Full GC (System) 75639.530: [CMS: 440238K->402641K(2682880K), 1.9246290 secs] 1356983K->402641K(4986880K), [CMS Perm : 127188K->127133K(211844K)], 1.9248950 secs] [Times: user=1.92 sys=0.01, real=1.93 secs]
2014-08-06T13:52:59.104+0800: 79241.456: [Full GC (System) 79241.456: [CMS: 453193K->405395K(2682880K), 1.8669940 secs] 989281K->405395K(4986880K), [CMS Perm : 130171K->130107K(212036K)], 1.8672510 secs] [Times: user=1.86 sys=0.01, real=1.87 secs] 


問題分析

從運維人員瞭解到,由於系統大量使用了NIO中的DirectByteBuffer,需要定期清理本地內存。

DirectByteBuffer通過內存映射,使java進程直接訪問與文件相關聯的虛擬地址空間,減少了文件拷貝帶來的開銷,提高了文件讀取效率。這一塊虛擬地址空間並不是分配在jvm堆上,而是分配在native堆上。yong gc不能回收這部分空間,只能通過Full gc順帶進行回收,那是因爲Full gc時會觸發sun.misc.Cleaner,對DirectByteBuffer對象做清理工作。所以之前的每隔1小時並不是真的想進行Full gc,而是想通過Full gc回收native堆中無用空間。

從上面的日誌可以看出每次Full gc,所花時間將近2秒,這2秒是停機回收,系統會有卡頓存在,用戶體驗不太好。可以從3個方面來解決這個問題:

1、禁用Full gc顯示調用(-XX:+DisableExplicitGC); 

2、減少Full gc次數;

3、降低每次Full gc的時間。

DisableExplicitGC

前面已提到系統大量用到了DirectByteBuffer,如果啓用了-XX:+DisableExplicitGC選項,在以下情況下會出現OOM:系統各方面性能良好,無Full gc且DirectByteBuffer所佔用的空間大於-Xmx分配的空間。因爲DirectByteBuffer會不斷地在native堆分配空間,它的引用進入了old區,old區保存大量的引用,而不能被回收,最終會導致native堆空間不足。

所以使用此選項有OOM風險,可以通過設置-XX:MaxDirectMemorySize=10m,使OOM來得更早一些。

減少Full gc次數

可以通過sun.rmi.dgc.server.gcInterval和un.rmi.dgc.client.gcInterval選項調整Full gc的時間間隔進而減少Full gc次數

降低每次Full gc的時間

通過ExplicitGCInvokesConcurrent選項,可以使用CMS收集器來觸發Full gc,添加這個選項之後的效果如下:

2014-08-06T19:21:22.394+0800: 0.640: [GC [1 CMS-initial-mark: 0K(2682880K)] 42849K(4986880K), 0.0016600 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2014-08-06T19:21:22.395+0800: 0.642: [CMS-concurrent-mark-start]
2014-08-06T19:21:22.483+0800: 0.729: [CMS-concurrent-mark: 0.027/0.087 secs] [Times: user=0.16 sys=0.00, real=0.09 secs]
2014-08-06T19:21:22.483+0800: 0.729: [CMS-concurrent-preclean-start]
2014-08-06T19:21:22.497+0800: 0.743: [CMS-concurrent-preclean: 0.007/0.014 secs] [Times: user=0.04 sys=0.00, real=0.01 secs]
2014-08-06T19:21:22.497+0800: 0.743: [CMS-concurrent-abortable-preclean-start]
 CMS: abort preclean due to time 2014-08-06T19:21:27.502+0800: 5.749: [CMS-concurrent-abortable-preclean: 2.415/5.006 secs] [Times: user=8.14 sys=0.68, real=5.01 secs]
2014-08-06T19:21:27.506+0800: 5.753: [GC[YG occupancy: 544921 K (2304000 K)]5.753: [Rescan (parallel) , 0.1339530 secs]5.887: [weak refs processing, 0.0000130 secs] [1 CMS-remark: 0K(2682880K)] 544921K(4986880K), 0.1340460 secs] [Times: user=0.52 sys=0.01, real=0.13 secs]
2014-08-06T19:21:27.641+0800: 5.887: [CMS-concurrent-sweep-start]
2014-08-06T19:21:27.641+0800: 5.887: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2014-08-06T19:21:27.641+0800: 5.887: [CMS-concurrent-reset-start]
2014-08-06T19:21:27.680+0800: 5.927: [CMS-concurrent-reset: 0.034/0.040 secs] [Times: user=0.08 sys=0.04, real=0.04 secs]
可以看出添加ExplicitGCInvokesConcurrent選項後,整個Full gc的停機時間(mark+remark)只有0.22秒,幾乎感覺不到。這裏需要提一下,相比Full gc,CMS收集器會花費更多的時間,如果對QPS比較敏感的應用應降低CMS觸發的次數。


總結

如果系統中大量使用了DirectByteBuffer,需要定期地對native堆做清理,清理時可以使用Full gc,也可以使用CMS,視QPS情況而定;
可以使用-Dsun.rmi.dgc.server.gcInterval=7200000與-Dsun.rmi.dgc.client.gcInterval=7200000 選項控制Full gc時間間隔;
可以使用-XX:DisableExplicitGC選項禁用顯示調用Full gc;
如果希望Full gc有更少的停機時間,可以啓用-XX:+ExplicitGCInvokesConcurrent或-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses選項。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章