JVM性能調(diào)優(yōu)--YGC

YGC
YGC頻次暫且忽略,問題主要集中在gc耗時上面。想要解決YGC耗時問題,首先要搞清楚YGC的耗時節(jié)點。(據(jù)我所知的YGC問題,還沒有逃出過這些維度)
存活對象掃描、標(biāo)記時間 存活對象copy to S區(qū),晉升到Old區(qū) 等待各線程到達安全點時間 GC日志輸出 操作系統(tǒng)活動(swap)
1、等待線程到達安全點
GC發(fā)生時,程序是會STW的(Serial, ParNew, Parallel Scanvange, ParallelOld, Serial Old全程都會STW,CMS等在初始標(biāo)記重新標(biāo)記階段也會STW), JVM這時候只運行GC線程,不運行用戶線程。
那JVM具體要在哪里,在什么時間點STW呢? 答:安全點。
因此,GC時,程序需要運行到最近的一個安全點(方法返回、循環(huán)結(jié)束、異常拋出等位置)停下來,安全點日志前面文章也提到了:
如果發(fā)現(xiàn) spin時間段表現(xiàn)異常,那么就要去查看下我們的代碼中是否有影響線程快速到達安全點的邏輯塊,比如大循環(huán)體等。
順便提一下,安全點的作用不只是進行GC,下面這些點都是會導(dǎo)致程序進入安全點的,其相關(guān)問題我們可以單開一篇來敘述
2、存活對象掃描和標(biāo)記
在程序進入安全點之后,下一步操作就是root對象的標(biāo)記和引用對象的可達性分析。
2.1 線程棧中的對象引用
這里需要引出兩個概念,OopMap和RememberedSet
為了獲取root對象,jvm需要從線程棧中找到堆內(nèi)存對象的引用,但是不一定都是引用,如果全棧掃描那就太浪費時間了,怎么辦,空間換時間(這個思路簡直是到處在用,在之前一篇面試相關(guān)的文章中我也有提到),
用OopMap維護對象引用,在到達安全點時,先更新oopMap,然后進行遍歷。然后由RememberedSet輔助進行可達性分析。
這里需要注意的是,雖然有了額外存儲空間的加持,但是整體的存活對象標(biāo)記耗時也是不好直接忽視的。
例:某網(wǎng)友的系統(tǒng),因為采用的調(diào)用方式有問題,導(dǎo)致系統(tǒng)中存活的業(yè)務(wù)線程高達幾千之多,那么,要遍歷這么多的OopMap的時間消耗也是非常大的。
還有一些程序,因為類似select等查詢語句沒有添加限制條件,導(dǎo)致大量數(shù)據(jù)加載進內(nèi)存,導(dǎo)致GC時間過長,甚至是OOM的發(fā)生。
因此,要關(guān)注對象引用的個數(shù),包括一個線程中的引用個數(shù)和總的線程個數(shù)。
2.2 本地緩存存放的對象過多
我們知道,如果對象存活的年限達到了設(shè)置的上限,是會晉升老年代的。如果有大量的本地緩存對象被創(chuàng)建,在其晉升到老年代之后,YGC會通過掃描card table來確認(rèn)其是否存活,從而增加YGC的是存活對象標(biāo)記時間。
因此,本地緩存方案要評估完整,或者考慮用堆外緩存減少本地緩存對GC的影響。
2.3 system class loader 加載對象過多
如果程序中有解析xml的邏輯時,每new一個XStream對象,就會新創(chuàng)建一個classloader,類加載器會和類的權(quán)限定名作為key,value為真正的Klass對象,會存儲在SystemDirectionary里,最終越來越多的存活對象存儲在內(nèi)存里,導(dǎo)致需要占用很長時間去標(biāo)記。
“The XStream instance is thread-safe. That is, once the XStream instance has been created and configured, it may be shared across multiple threads allowing objects to be serialized/deserialized concurrently”.
XStream官方的說法是XStream線程安全,不需要重復(fù)初始化xstream對象,為每個反序列化的對象聲明一個靜態(tài)的XStream,重復(fù)利用即可。
2.4JNI & Monitor & finalizable等
暫時沒有遇到過具體的異常實例,后續(xù)遇到了再補充吧。
3、存活對象Copy
可能不是特別準(zhǔn)確,因為機器1的GC不一定會發(fā)生,只是想用這個簡單的例子說明下eden區(qū)和新對象大小對GC copy的影響,進而對GC時間產(chǎn)生影響。
所以,要善于運用JVM監(jiān)控,預(yù)估每次調(diào)用的新對象和存活對象的大小,結(jié)合并發(fā)數(shù),設(shè)計合理的eden區(qū)和survivor區(qū)。
4、GC日志輸出
GC日志是比較讓人忽略的點,但是確實會對GC時間產(chǎn)生負(fù)面的影響。因為GC時的STW的總時間內(nèi),包含了GC日志打印的時間,正常情況下,輸出有限信息的GC日志對GC整體時間的影響應(yīng)該是微乎其微的,但是如果正好遇到了GC日志輸出時系統(tǒng)的IO負(fù)載很好,那么可能會在日志輸出這里等待很長的時間了。
一個解決辦法是將GC日志文件放到tmpfs上(例如,-Xloggc:/tmpfs/gc.log)。因為tmpfs沒有磁盤文件備份,所以tmpfs文件不會導(dǎo)致磁盤行為,因此也不會被磁盤IO阻塞。
reference:后臺IO異常導(dǎo)致的GC異常 https://www.pianshen.com/article/5926239581/
總結(jié):YGC耗時問題可以說千奇百怪,但是萬變不離其中,我們只要能夠掌握YGC的幾個關(guān)鍵節(jié)點涉及的影響,從原理著手分析,應(yīng)該是沒有什么大問題的~
