天天看點

一次簡單的 JVM 調優

背景

最近對負責的項目進行了一次性能優化,其中包括對 JVM 參數的調整,算是進行了一次簡單的 JVM 調優,JVM 參數調整之後,服務的整體性能有 5% 左右的提升,還算不錯。

先介紹一下項目的基本情況:

項目是一個高 ​​

​QPS ​

​​壓力的 web 服務,單機 ​

​QPS ​

​​一直維持在 1.5K 以上,由于舊機器的”拖累”,配置的堆大小是 8G,其中 young 區是 4G,垃圾回收器用的是 ​

​parNew + CMS​

​。

舊狀

首先是檢視目前 GC 的情況,主要是使用 ​

​jstat​

​​ 檢視 GC 的概況,再檢視 gc log,分析單次 gc 的詳細狀況。

使用 ​​

​jstat -gcutil pid 1000​

​ 每隔一秒列印一次 gc 統計資訊。

一次簡單的 JVM 調優

可以看到,單次 gc 平均耗時是 60ms 左右,還算可以接受,但 YGC 非常頻繁,基本上每秒一次,有的時候還會一秒兩次,在一秒兩次的時候,服務對業務響應時長的壓力就會變得很大。

接着檢視 gc log,列印 gc log 需要在 JVM 啟動參數裡添加以下參數:

  • ​-XX:+PrintGCDateStamps​

    ​:列印 gc 發生的時間戳。
  • ​-XX:+PrintTenuringDistribution​

    ​:列印 gc 發生時的分代資訊。
  • ​-XX:+PrintGCApplicationStoppedTime​

    ​:列印 gc 停頓時長
  • ​-XX:+PrintGCApplicationConcurrentTime​

    ​:列印 gc 間隔的服務運作時長
  • ​-XX:+PrintGCDetails​

    ​:列印 gc 詳情,包括 gc 前/記憶體等。
  • ​-Xloggc:../gclogs/gc.log.date​

    ​:指定 gc log 的路徑

看到的 gc log 形如:

一次簡單的 JVM 調優

單次 GC 方面并不能直接看出問題,但可以看到 gc 前有很多次 18ms 左右的停頓。

分析和調整

YGC 頻繁

直接檢視 gc log 并不直覺,我們可以借用一些可視化工具來幫助我們分析, ​

​[gceasy](https://gceasy.io/)​

​​ 是個挺不錯的網站,我們把 gc log 上傳上去後, gceasy 可以幫助我們生成各個次元的圖表幫助分析。

檢視 gceasy 生成的報告,發現我們服務的 gc 吞吐量是 95%,它指的是 JVM 運作業務代碼的時長占 JVM 總運作時長的比例,這個比例确實有些低了,運作 100 分鐘就有 5 分鐘在執行 gc。幸好這些 GC 中絕大多數都是 YGC,單次時長可控且分布平均,這使得我們服務還能平穩運作。

解決這個問題要麼是減少對象的建立,要麼就增大 young 區。前者不是一時半會兒都解決的,需要查找代碼裡可能有問題的點,分步優化。

而後者雖然改一下配置就行,但以我們對 GC 最直覺的印象來說,增大 young 區,YGC 的時長也會迅速增大。

其實這點不必太過擔心,我們知道 YGC 的耗時是由 ​​

​GC 标記 + GC 複制​

​​ 組成的,相對于 GC 複制,GC 标記是非常快的。而 young 區内大多數對象的生命周期都非常短,如果将 young 區增大一倍,GC 标記的時長會提升一倍,但到 GC 發生時被标記的對象大部分已經死亡, GC 複制的時長肯定不會提升一倍,是以我們可以放心增大 young 區大小。

由于低記憶體舊機器都被換掉了,我把堆大小調整到了 12G,young 區保留為 8G。

分代調整

除了 GC 太頻繁之外,GC 後各分代的平均大小也需要調整。

一次簡單的 JVM 調優

我們知道 GC 的提升機制,每次 GC 後,JVM 存活代數大于 ​

​MaxTenuringThreshold​

​​ 的對象提升到老年代。

當然,JVM 還有動态年齡計算的規則:按照年齡從小到大對其所占用的大小進行累積,當累積的某個年齡大小超過了 survivor 區的一半時,取這個年齡和 MaxTenuringThreshold 中更小的一個值,作為新的晉升年齡門檻值,但看各代總的記憶體大小,是達不到 survivor 區的一半的。

一次簡單的 JVM 調優

是以這十五個分代内的對象會一直在兩個 survivor 區之間來回複制,再觀察各分代的平均大小,可以看到,四代以上的對象已經有一半都會保留到老年區了,是以可以将這些對象直接提升到老年代,以減少對象在兩個 survivor 區之間複制的性能開銷。

是以我把 MaxTenuringThreshold 的值調整為 4,将存活超過四代的對象直接提升到老年代。

偏向鎖停頓

結果

小結

繼續閱讀