默認垃圾回收器
JDK8要使用CMS,那么必須顯示申明,因為它采用的默認垃圾回收器是ParallelGC。
顯示申明垃圾回收器為CMS+parNew非常簡單,只需要添加如下兩個JVM參數:
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
校驗JVM參數的網址:https://opts.console.heapdump.cn/,這里我們可以用到它的“參數檢查”。
堆大小
接下來,最重要的就是申明年輕代和老年代的大小。由於采用的CMS+ParNew。建議堆大小不要超過8G,最好6G以內,因為CMS+ParNew組合情況下發生的FGC是采用MSC算法且單線程回收,如果堆內存很大,FGC時STW時間會非常恐怖。筆者這里以4G舉例,這時候再添加幾個JVM參數,我們得到如下的配置。這里筆者設置的年輕代大概是1.5G,老年代大概是2.5G。這算是一個比較合理的比例搭配。如果你的JVM參數這樣搭配但是GC情況仍然不是很好,那么可能需要根據你的業務特性進行特別的調優:
-Xmx4g -Xms4g -Xmn1512m
線程棧
JDK8默認的線程棧大小為1M,有點偏大。以筆者的經驗,絕大部分微服務項目是可以調整為512k,甚至256k的(筆者的項目就是256k,運行的棒棒噠):
-Xss256k
Old回收閾值
既然配置的是CMS,那么如下兩個參數一定要加上。為什么要加上這兩個JVM參數呢?這是因為CMS回收條件非常復雜,如果不通過CMSInitiatingOccupancyFraction
和UseCMSInitiatingOccupancyOnly
限制只在老年代達到75%才回收的話(這個閾值可以根據具體情況適當調整),當線上碰到一些CMS GC時,是很難搞清楚原因的:
-XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly
CMS GC觸發條件相關文章推薦--【JVM 源碼解讀之 CMS GC 觸發條件】:https://mp.weixin.qq.com/s/Mu-Xz4CLgdxJhcMJ7aKAHg
元數據空間
如果是微服務架構,那么對於絕大部分應用來說,128M的元數據完全夠用。不過,JDK8的元數據空間並不是指定多少就初始化多大的空間。而是按需擴展元數據空間。所以,我們可以設置256M。如果不設置這兩個參數的話,元數據空間默認初始化只有20M出頭,那么就會在應用啟動過程中,Metaspace擴容發生FGC:
-XX:MaxMetaspaceSize=256m -XX:MetaspaceSize=256M
dump路徑
設定如下兩個參數(需要說明的是,HeapDumpPath參數指定的路徑需要提前創建好,JVM沒辦法在生成dump文件時創建該目錄),當JVM內存導致導致JVM進程退出時能自動在該目錄下生成dump文件:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/log/jvmdump/
GC日志
這個必須有,不然線上環境GC問題都不好排查。並且loggc所在目錄(/data/log/gclog/)和dump路徑一樣,必須提前創建好,JVM無法自動創建該目錄:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/data/log/gclog/gc.log
壓縮
我們都知道,CMS GC是並發的垃圾回收器,它采用的是標記清除算法,而不是壓縮算法。意味着隨着時間的推移,碎片會越來越多,JVM終究會觸發內存整理這個動作。那么,什么時候整理內存碎片呢?跟下面兩個參數有很大的關系。第一個參數是開啟這個能力,第二個參數表示在壓縮(compaction)內存之前需要發生多少次不壓縮內存的FGC。CMS GC不是FGC,在CMS GC搞不定的時候(比如:concurrent mode failure),會觸發完全STW但不壓縮內存的FGC(假定命名為NoCompactFGC),或者觸發完全STW並且壓縮內存的FGC(假定命名為CompactFGC)。所以,這個參數的意思就是,連續多少次NoCompactFGC后觸發CompactFGC。如果中間出現了CMS GC,那么又需要重新計數NoCompactFGC發生的次數。
one more
最后,再推薦給大家一個搭配CMS時很好用的JVM參數,如下所示。官方對該參數的說明為:A System.gc() request invokes a concurrent collection and also unloads classes during such a concurrent gc cycle (effective only when UseConcMarkSweepGC)。這句話總結如下:1、只有在使用CMS時才有效。2、當調用System.gc()時會用CMS這個並行垃圾回收器去進行回收(比如大量使用DirectByteBuffer進行堆外內存操作,需要FGC來回收堆外內存的場景。就可以通過該參數讓本來需要FGC才能搞定的事情用CMS GC就可以搞定了)。3、除了能喚起並行垃圾回收器,還能卸載類。
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
最終,得到我們配置的完整的JVM參數配置如下(此參數在以前筆者負責的一個微服務項目中運行了數年,單機並發1000+,CMS GC大概是8天左右一次):
-Xms4g -Xmx4g -Xmn1512m -server -Xss256k -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/data/log/gclog/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/log/jvmdump/ -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:+TieredCompilation -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses