作者簡介:大家好,我是smart哥,前中興通訊、美團架構(gòu)師,現(xiàn)某互聯(lián)網(wǎng)公司CTO
聯(lián)系qq:184480602,加我進群,大家一起學(xué)習(xí),一起進步,一起對抗互聯(lián)網(wǎng)寒冬
學(xué)習(xí)必須往深處挖,挖的越深,基礎(chǔ)越扎實!
階段1、深入多線程
階段2、深入多線程設(shè)計模式
階段3、深入juc源碼解析
階段4、深入jdk其余源碼解析
階段5、深入jvm源碼解析
一、案例背景
本章將介紹一個因為大對象而導(dǎo)致的頻繁GC問題,其本質(zhì)也是開發(fā)童鞋在寫程序代碼時存在bug,進而導(dǎo)致出現(xiàn)JVM性能問題。
首先,這個系統(tǒng)上線之后發(fā)現(xiàn)一天的Full GC次數(shù)多達幾十次,通常來說,我們建議的一個比較良好的JVM性能,應(yīng)該是Full GC幾天才發(fā)生一次,或者最多一天發(fā)生幾次而已。
生產(chǎn)環(huán)境這個系統(tǒng)部署在2核4G的機器上,JVM參數(shù)如下:-Xms=1536M -Xmx=1536M -Xmn=512M -Xss=256K -XX:SurvivorRatio=5 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=68 -XX:CMSParallelRemarkEnable -XX:UseCMSInitiatingOccupancyOnly -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:PrintHeapAtGC -Xloggc:gc.log
比較關(guān)鍵的幾個設(shè)置是:
- -XX:SurvivorRatio=5:表示Eden:Survivor:Survivor=5:1:1
- -XX:CMSInitiatingOccupancyFraction=68:表示一旦老年代內(nèi)存使用達到68%,就會觸發(fā)Full GC
此時,整個系統(tǒng)對應(yīng)的JVM內(nèi)存模型如下:
1.1 存在問題
我們通過jstat進行分析,統(tǒng)計出的JVM性能如下:
- 系統(tǒng)運行時間:6天
- 6天內(nèi)的Full GC次數(shù)和總耗時:250次,70秒
- 6天內(nèi)的Young GC次數(shù)和總耗時:26000次,1400秒
也就是說,平均每20s觸發(fā)一次Young GC,每30分鐘觸發(fā)一次Full GC。根據(jù)Eden區(qū)和老年代的空間可以估算,系統(tǒng)每秒鐘會產(chǎn)生約20MB對象進入Eden,每30分鐘會有約600MB對象進入老年代:
根據(jù)參數(shù)-XX:CMSInitiatingOccupancyFraction=68
,老年代內(nèi)存使用達到68%,就會觸發(fā)Full GC,一次Full GC時間約300ms。
二、大對象
2.1 優(yōu)化前
通過上述的案例背景介紹,我們首先想到的是會不會因為Survivor區(qū)太小,導(dǎo)致Young GC后的存活對象太多,放不下Survivor了,所以就一直有對象流入老年代,進而導(dǎo)致30分鐘后觸發(fā)Full GC?
但這只是推論,因為對象進入老年代也可能是因為動態(tài)年齡判斷規(guī)則,所以我們就需要通過工具在高峰期觀察JVM的內(nèi)存使用情況。
事實上,我們觀察到每次 Young GC后進入老年代的對象非常少,而且一次Young GC的存活對象也就是幾十MB,Survior區(qū)可以容納,偶爾觸發(fā)動態(tài)年齡判斷規(guī)則時,才有幾十MB對象進入老年代:
因此,分析到這里就很奇怪了,因為通過jstat追蹤,并不是每次Young GC后都有幾十MB對象進入老年代,而是偶爾才有對象進入老年代,記住,是偶爾。
那么老年代里面到底為什么會有那么多對象呢?
我們觀察發(fā)現(xiàn),?系統(tǒng)運行一段時間后,突然間老年代中的對象就會增加五六百MB?。
答案已經(jīng)很明顯了——?大對象?!一定是系統(tǒng)運行時,每隔一段時間就會產(chǎn)生幾百兆的大對象,直接進入老年代,不會走年輕代的Eden區(qū),然后配合年輕代還偶爾會有幾十MB對象進入老年代,所以才30分鐘觸發(fā)一次Full GC:
2.2 優(yōu)化后
了解了問題的所在,我們就開始針對這個案例進行優(yōu)化。首先,就是要確定這個大對象到底是什么?
我們采用jmap工具導(dǎo)出一份JVM內(nèi)存快照,然后通過jhat或者Visual VM之類的可視化工具進行分析,發(fā)現(xiàn)那幾百兆大對象就是從數(shù)據(jù)庫中查出的記錄,然后對SQL進行排查,發(fā)現(xiàn)某個SQL在一種特殊場景下會執(zhí)行類似SELECT * FROM
的語句,導(dǎo)致一次性從數(shù)據(jù)庫中查出幾十萬條數(shù)據(jù)。
針對該問題,主要做以下幾點優(yōu)化:
- 解決程序bug,不允許全表查詢,這樣就避免了大對象直接進入老年代的問題;
- Survivor區(qū)明顯不夠,70MB的空間很容易觸發(fā)動態(tài)年齡判斷,所以為其分配更多空間。
優(yōu)化后的JVM參數(shù)如下:
-Xms=1536M -Xmx=1536M -Xmn=1024M -Xss=256K -XX:SurvivorRatio=5 -XX:PermSize=256M -XX:MaxPermSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=92 -XX:CMSParallelRemarkEnable -XX:UseCMSInitiatingOccupancyOnly -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:PrintHeapAtGC -Xloggc:gc.log
可以看到,新生代直接分配1G空間,其中Survivor各占150MB左右,此時Young GC過后的幾十MB存活對象一般就不會進入老年代了。
同時,調(diào)整參數(shù)-XX:CMSInitiatingOccupancyFraction=92
,將比例提高至92%,避免老年代僅占62%就觸發(fā)Full GC。
最后,還設(shè)置永久代大小為256MB,因為默認(rèn)永久代就幾十MB,如果程序使用了反射等機制,很容
易被占滿。
經(jīng)過上述優(yōu)化,系統(tǒng)基本上每分鐘一次Young GC,幾天才會發(fā)生一個Full GC。文章來源:http://www.zghlxwxcb.cn/news/detail-797608.html
三、總結(jié)
本章,我們通過示例分析了大對象導(dǎo)致的頻繁Full GC問題,并一步一步展現(xiàn)了發(fā)現(xiàn)問題、分析問題、解決問題的思路。當(dāng)我們發(fā)現(xiàn)Young GC過后并不是每次都有很多存活對象進入老年代的時候,就要從別的角度考慮下到底為什么會有那么多存活對象進入老年代。文章來源地址http://www.zghlxwxcb.cn/news/detail-797608.html
到了這里,關(guān)于JVM實戰(zhàn)(24)——大對象優(yōu)化的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!