JDK8的GC類型 與 高併發生產環境中不同GC類型帶來的性能提升

    記一次項目中,在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以後支持。
  • 新生代老年代兩種回收機制可搭配在一起工作圖如下

    • 新生代老年代搭配使用方式
    • 上圖中黃色區域是年輕態,藍色框子代表適用的幾種垃圾回收方式;下方白色區域代表舊生代,藍色也是代表在舊生代的回收方式,兩種藍色盒子之間的連線表示它們的搭配配置, 比如Serial只能和CMS或Serial Old搭配使用,而ParNew只能和CMS或Serial Old使用,而Parallel Scavenge只能和Serail Old和Parallel Old使用,不能和CMS搭配使用。
  • 在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
      

驗證結論(GC都是會Stop The Word的)

  1. 通過比較發現ParNew類型的YoungGC平均StopTheWord時間明顯短於PS Scavenge(壓測期間服務器的表現如下圖) Scavenge
  2. 通過比較發現CMS類型的OldGC平均StopTheWord時間明顯短於PS MarkSweep(壓測期間服務器的表現如下圖) CMS
  3. 大家都知道GC是會Stop The Word的,也就是會暫停Web服務器業務接口的響應,經過上面的比較,可以看到CMS配合使用的GC性能效果更優,可以提高系統吞吐量

作者爲什麼做這樣的分享與總結

  1. 作者所在公司與全國第三大流量電商(某多多)有合作,他們使用了我們的一款產品。他們對我們產品提出了非常嚴格的要求:一分鐘時間範圍內接口響應時間大於300MS的要小於20個,一天24時間內這樣的情況出現次數少於3次。 產品剛開始上線,在下午流量最大的時候,超時頻率逐漸增加,沒有達到大廠的要求,作者就開始潛心分析問題、總結規律、分析GC日誌,找到大概原因後,做了大量測壓力測試,各種參數調優後再做壓力測試,做各種壓力測試效果比較,最終在生產環境上線,得到了非常好的效果。 作者公司的產品是單體Web應用,扛下了第三大流量電商的服務,主流工作時間10小時內:接口調用在200W次左右,性能表現非常好。
  2. 調優前的服務器表現(剛上線的時候) 線上調優前
  3. 調優後的服務器表現 線上調優後
  4. 可以明顯看到服務器CPU消耗較平穩,沒有GC驟然佔用CPU很多的情況出現。

GC額外配置 最佳實戰說明

  1. -XX:NewRatio=3:設置新生代與老年代的比例(需要根據企業產品的特徵來做不同的設置:程序開闢內存駐留時間長短,手動銷燬內存等)
  2. -XX:SurvivorRatio=8:設置新生代中Eden與Survivor的比例(需要根據企業產品的特徵來做不同的設置,一般需要在測試環境做不同配置參數的壓力測試,從而得到最佳配置)
  3. -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'

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章