記一次項目中,在JDK8環境下,並行GC月併發GC在Restful接口中體現差異。
當今國內第三大流量電商(某多多),博主所在公司在前一段時間和他們有產品合作,他們對我們產品提出了非常嚴格的要求:一分鐘時間範圍內接口響應時間大於300MS的要小於20個,一天24時間內這樣的情況出現次數少於3次。 產品剛開始上線,在下午流量最大的時候,超時頻率逐漸增加,沒有達到大廠的要求,博主就開始潛心分析問題、總結規律、分析GC日誌,找到大概原因後,做了大量測壓力測試,各種參數調優後再做壓力測試,做各種壓力測試效果比較,最終在生產環境應用,得到了非常好的效果。 作者公司的產品是單體Web應用,扛下了第三大流量電商的服務,主流工作時間10小時內:接口調用在200W次左右,性能表現非常好。
本博文就以上面的實際案例做爲線索,詳細講解JDK8的不同GC類型,並以實際可運行項目驗證不同GC對生產系統帶來的不同性能表現。驗證項目的代碼在https://github.com/yun19830206/JavaTechnicalSummary/tree/master/Technology_Experience/Jdk7GC
GC知識介紹
-
新生代
幾種垃圾收集方式:- Serial (複製) 是一種stop-the-world(導致應用全部暫停,僵死一會兒), 使用單個GC線程進行復制收集。 將倖存對象從 Eden複製到倖存 Survivor空間,並且在倖存Survivor空間之間複製,直到它決定這些對象已經足夠長了,在某個點一次性將它們複製到舊生代old generation.
- Parallel Scavenge (PS Scavenge)是一種stop-the-world, 使用多個GC線程實現複製收集。如同上面複製收集一樣,但是它是並行使用多個線程。
- ParNew是一種stop-the-world, 使用多個GC線程實現的複製收集,區別於"Parallel Scavenge"在於它與CMS可搭配使用,它也是並行使用多個線程,內部有一個回調功能允許舊生代操作它收集的對象。
-
老年代
幾種垃圾收集方式:- Serial Old (MarkSweepCompact) 是一種stop-the-world, 使用單個線程進行mark-sweep-compact(標誌-清掃-壓縮) 收集。
- Parallel Old (PS MarkSweep) 是一種使用多個GC線程壓縮收集。
- ConcurrentMarkSweep (CMS) 是最並行,低暫停的收集器。垃圾回收算法在後臺不會暫停應用線程情況下實現大部分垃圾回收工作。
- G1 使用 'Garbage First' 算法將堆空間劃分爲許多小空間。是一種跨年輕態和舊生代的回收。Java 7以後支持。
-
新生代
與老年代
兩種回收機制可搭配在一起工作圖如下 -
在JVM中是+XX配置實現的搭配組合:
- UseSerialGC 表示 "Serial" + "Serial Old"組合
- UseParNewGC 表示 "ParNew" + "Serial Old"
- UseConcMarkSweepGC 表示 "ParNew" + "CMS". 組合,"CMS" 是針對舊生代使用最多的
- UseParallelGC 表示 "Parallel Scavenge" + "Serial Old"組合
- UseParallelOldGC 表示 "Parallel Scavenge" + "Parallel Old"組合
-
在實踐中使用UseConcMarkSweepGC 表示 "ParNew" + "CMS" 的組合是經常使用的
JDK8不同啓動方式所啓用的GC介紹
- JDK8默認啓動參數:-Xms2048M -Xmx2048M -XX:+PrintGC -Xloggc:/log/Jdk7GC.GCDeatil.log
- 垃圾回收器的名稱:Parallel Scavenge (PS Scavenge)(新生代)
- 垃圾回收器的名稱:Parallel Old (PS MarkSweep) (老年代)
- 等於啓動參數使用 -XX:+UseParallelOldGC
- JDK8使用UseConcMarkSweepGC啓動參數:-Xms2048M -Xmx2048M -XX:+UseConcMarkSweepGC -XX:+PrintGC -Xloggc:/log/Jdk7GC.GCDeatil.log
- 垃圾回收器的名稱:ParNew(新生代)
- 垃圾回收器的名稱:ConcurrentMarkSweep (老年代)
打包運行被測試的WebApp(注意後面增加遠程監控Java VisualVM的方法)
- 打包SpringBoot Jar 並上傳到服務器特定目錄
- JDK8默認啓動參數
- nohup java $JAVA_OPTS -Xms6444M -Xmx6444M -XX:+PrintGC -Xloggc:/yun/log/Jdk8GC.GCDeatil.log -jar jdk8gc-0.0.1-SNAPSHOT.jar > jdk8gclog.file 2>&1 &
- JDK8 使用CMS垃圾收集器使用
- nohup java $JAVA_OPTS -Xms6444M -Xmx6444M -XX:+UseConcMarkSweepGC -XX:+PrintGC -Xloggc:/yun/log/Jdk8GC.GCDeatil.log -jar jdk8gc-0.0.1-SNAPSHOT.jar > jdk8gclog.file 2>&1 &
測試方法入口
- 測試地址:http://hostName:8080/jdk7gcperformance/dobusiness
- 統一使用JUnit代碼測試遠程接口 5個線程併發,每個線程循環執行4000次,測試入口在本工廠源碼當中:Jdk7GcApplicationTests.jdk8GcRequest()方法
- 測試JDK8默認啓動參數的效果如下:nohup java $JAVA_OPTS -Xms6444M -Xmx6444M -XX:+PrintGC -Xloggc:/yun/log/Jdk8GC.GCDeatil.log -jar jdk8gc-0.0.1-SNAPSHOT.jar > jdk8gclog.file 2>&1 &
-
==》不同垃圾收集器的工作的次數與總耗時 垃圾回收器的名稱:PS Scavenge 垃圾回收器已回收的總次數:239 垃圾回收器已回收的總時間:32秒 垃圾回收器的名稱:PS MarkSweep 垃圾回收器已回收的總次數:82 垃圾回收器已回收的總時間:24秒 ==》平均每次YoungGC時間0.133S, 每次OldGC時間0.292S
==》監測1分鐘時間內接口響應時間大於300MS的頻率 1分鐘內接口響應時間大於300MS:開始時間=1542771846283, 次數=79 1分鐘內接口響應時間大於300MS:開始時間=1542771906334, 次數=42 1分鐘內接口響應時間大於300MS:開始時間=1542771966366, 次數=40 1分鐘內接口響應時間大於300MS:開始時間=1542772026519, 次數=58
- 測試JDK8使用CMS垃圾收集器效果如下: nohup java $JAVA_OPTS -Xms6444M -Xmx6444M -XX:+UseConcMarkSweepGC -XX:+PrintGC -Xloggc:/yun/log/Jdk8GC.GCDeatil.log -jar jdk8gc-0.0.1-SNAPSHOT.jar > jdk8gclog.file 2>&1 &
==》不同垃圾收集器的工作的次數與總耗時 垃圾回收器的名稱:ParNew 垃圾回收器已回收的總次數:1549 垃圾回收器已回收的總時間:52秒 垃圾回收器的名稱:ConcurrentMarkSweep 垃圾回收器已回收的總次數:44 垃圾回收器已回收的總時間:1秒 ==》平均每次YoungGC時間0.033S, 每次OldGC時間0.022S
==》監測1分鐘時間內接口響應時間大於300MS的頻率 1分鐘內接口響應時間大於300MS:開始時間=1542771349156, 次數=33 1分鐘內接口響應時間大於300MS:開始時間=1542771409216, 次數=30 1分鐘內接口響應時間大於300MS:開始時間=1542771469206, 次數=38 1分鐘內接口響應時間大於300MS:開始時間=1542771529249, 次數=28
- 測試JDK8使用CMS垃圾收集器效果如下: nohup java $JAVA_OPTS -Xms6444M -Xmx6444M -XX:+UseConcMarkSweepGC -XX:+PrintGC -Xloggc:/yun/log/Jdk8GC.GCDeatil.log -jar jdk8gc-0.0.1-SNAPSHOT.jar > jdk8gclog.file 2>&1 &
驗證結論(GC都是會Stop The Word的)
- 通過比較發現ParNew類型的YoungGC平均StopTheWord時間明顯短於PS Scavenge(壓測期間服務器的表現如下圖)
- 通過比較發現CMS類型的OldGC平均StopTheWord時間明顯短於PS MarkSweep(壓測期間服務器的表現如下圖)
- 大家都知道GC是會Stop The Word的,也就是會暫停Web服務器業務接口的響應,經過上面的比較,可以看到CMS配合使用的GC性能效果更優,可以提高系統吞吐量
作者爲什麼做這樣的分享與總結
- 作者所在公司與全國第三大流量電商(某多多)有合作,他們使用了我們的一款產品。他們對我們產品提出了非常嚴格的要求:一分鐘時間範圍內接口響應時間大於300MS的要小於20個,一天24時間內這樣的情況出現次數少於3次。 產品剛開始上線,在下午流量最大的時候,超時頻率逐漸增加,沒有達到大廠的要求,作者就開始潛心分析問題、總結規律、分析GC日誌,找到大概原因後,做了大量測壓力測試,各種參數調優後再做壓力測試,做各種壓力測試效果比較,最終在生產環境上線,得到了非常好的效果。 作者公司的產品是單體Web應用,扛下了第三大流量電商的服務,主流工作時間10小時內:接口調用在200W次左右,性能表現非常好。
- 調優前的服務器表現(剛上線的時候)
- 調優後的服務器表現
- 可以明顯看到服務器CPU消耗較平穩,沒有GC驟然佔用CPU很多的情況出現。
GC額外配置 最佳實戰說明
- -XX:NewRatio=3:設置新生代與老年代的比例(需要根據企業產品的特徵來做不同的設置:程序開闢內存駐留時間長短,手動銷燬內存等)
- -XX:SurvivorRatio=8:設置新生代中Eden與Survivor的比例(需要根據企業產品的特徵來做不同的設置,一般需要在測試環境做不同配置參數的壓力測試,從而得到最佳配置)
- -Xms6144M -Xmx6144M -XX:PermSize=128M -XX:MaxPermSize=256M: 這些較爲常見不做詳細介紹。-XX:MetaspaceSize是JDK8裏面的。
服務器運行SpringBoot打包後的Jar,並開啓Java VisualVM遠程監控 辦法
- 進入JAVA_HOME\jre\lib\management\目錄
- 拷貝jmxremote.password.template這個文件到當前目錄, 並改名爲jmxremote.password
- 打開jmxremote.password文件,去掉 # monitorRole QED 和 # controlRole R&D 這兩行前面的註釋符號
- 編輯文件,命令:vim /etc/profile, 在最後增加如下內容(注意IP地址必須是外網能訪問的地址,並非內網地址)
export JAVA_OPTS='-Djava.rmi.server.hostname=52.80.111.111 -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9001 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false'
- 應用最新配置信息,命令:source /etc/profile
- 啓動服務:nohup java $JAVA_OPTS -Xms6144M -Xmx6144M -XX:+PrintGC -Xloggc:/log/Jdk8GC.GCDeatil.log -jar jdk7gc-0.0.1-SNAPSHOT.jar > jdk8gclog.file 2>&1 &
- 注意不要分多個窗口執行上面的命令,不然可能會因爲配置文件不生效(source /etc/profile)導致遠程監控不生效
- 排查遠程監控不成功的方法,在ps -ef | grep java 的結果中,可以看到啓動參數
- 參考《https://blog.csdn.net/u011391839/article/details/76984995》《https://blog.csdn.net/luosai19910103/article/details/75574725》