1. 程式人生 > >JDK8的GC型別 與 高併發生產環境中不同GC型別帶來的效能提升

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'