警惕大量類加載器的創(chuàng)建導致詭異的Full GC
言歸正傳,今天有個同事找我,其實好像之前就找過我,一直因為太忙,后面就忘記他的事了,到今天還沒查出原因就又找了過來,現(xiàn)象是系統(tǒng)老是進行Full GC,在啟動沒過多久就會發(fā)生Full GC,這個現(xiàn)象相對比較少見的,于是找他要了GC日志,赫然看到如下日志:
這個很顯然就是達到了Metaspace的閾值觸發(fā)的Full GC了,但是看看Metaspace的size,使用了134M左右,于是我詢問他MetaspaceSize和MaxMetaspaceSize分別設(shè)置了多少,告知我設(shè)置的是256M,那就有幾個比較奇怪的地方了:
- 為什么啟動沒多久就因為Metaspace觸發(fā)了Full GC
- 從使用率來看并沒有達到閾值
- 在Full GC之后立馬就能正常運行一段時間,說明Metaspace確實回收了
先說個JVM的BUG
從上面的GC日志,我們看到了Full GC前后,Metaspace的使用變化是從137752K->71671K,其實你們?nèi)绻玫膐racle官方的JDK,看到的會是137752K->137752K,也就是并沒有發(fā)生變化,看起來好像Metaspace并沒有被回收,其實這是JVM的一個BUG,我們alijdk將這個問題進行了修復,能看到前后是有變化的,所以如果大家在排查Metaspace的問題時候,希望不要被這個信息騙到
再聊點GC日志
從JDK8開始,任何GC,都會默認打印GC Cause,所以你看到上面的Full GC是因為Metadata GC Threshold觸發(fā)的,也就是Metaspace committed的內(nèi)存加上這次要分配的內(nèi)存達到了MetaspaceSize的閾值。如果是JDK7(之前版本不支持),那可以通過加JVM參數(shù)-XX:+PrintGCCause來打印原因,可以通過下面的圖片小程序鏈接點進去看看這個參數(shù)的具體用法及含義:
再提一點,Metaspce觸發(fā)的GC都是Full GC。
另外大家常看到的類似下面的Allocation Failure的GC Cause,其實是正常的,因為大部分GC,尤其是YGC,都是因為分配內(nèi)存失敗才觸發(fā)的,所以不要認為看到Failure就覺得有問題。
為何使用率這么低就觸發(fā)了Full GC
Metaspace觸發(fā)Full GC,是因為Metaspace committed的內(nèi)存加上這次要分配的內(nèi)存之和超過了閾值才會觸發(fā),但是我們看使用了才134M,而閾值卻是256M,那可能懷疑下面兩種情況:
這次分配的內(nèi)存達到122M以上?
碎片化問題?
對于***種情況,基本不太可能,因為一個類不可能要這么大內(nèi)存,所以暫時先排除這種可能。
對于第二種情況,有一個場景是能滿足的,類加載器創(chuàng)建非常多,但是每個類加載器加載的類又特別少,同時Full GC之后又能很快被回收掉
為了驗證第二種情況,我嘗試加兩個參數(shù)-XX:+HeapDumpBeforeFullGC和-XX:+HeapDumpAfterFullGC,在Full GC前后分別對內(nèi)存做一個dump,這兩個參數(shù),可以通過下面的圖片小程序鏈接看到具體的使用情況
從兩個dump的分析結(jié)果來看,查了下類加載器的情況,果然在Full GC之前看到了31650個類加載器,而Full GC之后,類加載器個數(shù)變成了872個,于是開始找究竟是哪些類加載器,最終發(fā)現(xiàn)某個特定類型的類加載器對象非常之多,咨詢了業(yè)務方確實存在這種情況,因為沒有做好緩存,所以導致了無止境創(chuàng)建
類加載器過多為什么會導致Full GC
類加載器創(chuàng)建過多,帶來的一個問題是,在類加載器***次加載類的時候,會在Metaspace里會給它分配內(nèi)存塊,為了分配高效,每個類加載器用來存放類信息的內(nèi)存塊都是獨立的,所以哪怕你這個類加載器只加載一個類,也會為之分配一塊空的內(nèi)存給這個類加載器,其實是至少兩個內(nèi)存塊,于是你有可能會發(fā)現(xiàn)Metaspace的內(nèi)存使用率非常低,但是committed的內(nèi)存已經(jīng)達到了閾值,從而觸發(fā)了Full GC,如果這種只加載很少類的類加載器非常多,那造成的后果就是很多碎片化的內(nèi)存
JVMPocket介紹
JVMPocket是我最近搗鼓的一個微信小程序,大家可以通過搜索JVMPocket或者從我公眾號菜單里進入,該小程序主要緣因JVM參數(shù)而誕生,有人問我相關(guān)的問題,告訴他們什么參數(shù)可以解決,但是苦于參數(shù)太長而無法記住,特尷尬,有了JVMPocket之后,直接找到對應的參數(shù)發(fā)個鏈接過去就可以看到對應參數(shù)的具體含義,用法,默認值以及大家的使用建議等,希望該小程序也能幫到大家,大家如果自己的JVM參數(shù)經(jīng)驗,都可以到對應的參數(shù)下面留言讓更多人知道它背后的故事。
JVMPocket
JVM參數(shù)錦囊
【本文是51CTO專欄作者李嘉鵬的原創(chuàng)文章,轉(zhuǎn)載請通過微信公眾號(你假笨,id:lovestblog)聯(lián)系作者本人獲取授權(quán)】