愛鋒貝

標題: JVM 調優(yōu)之提升 GC 吞吐量 [打印本頁]

作者: 邢義數(shù)碼君    時間: 2023-4-3 16:00
標題: JVM 調優(yōu)之提升 GC 吞吐量
1. 背景

最近對負責的項目進行了一次性能優(yōu)化,其中包括對 JVM 參數(shù)的調整,算是進行了一次簡單的 JVM 調優(yōu),JVM參數(shù)調整之后,服務的整體性能有 5% 左右的提升,還算不錯。
先介紹一下項目的基本情況:
項目是一個高 QPS 壓力的 web 服務,單機 QPS 一直維持在 1.5K 以上,由于舊機器的拖累,配置的堆大小是 8G,其中 young 區(qū)是 4G,垃圾回收器用的是 parNew + CMS。
2. 舊狀

首先是查看當前 GC 的情況,主要是使用 jstat 查看 GC 的概況,再查看 gc log,分析單次 gc 的詳細狀況。使用 jstat -gcutil pid 1000 每隔一秒打印一次 gc 統(tǒng)計信息。

(, 下載次數(shù): 79)
可以看到,單次 gc平均耗時是 60ms 左右,還算可以接受,但 YGC 非常頻繁,基本上每秒一次,有的時候還會一秒兩次,在一秒兩次的時候,服務對業(yè)務響應時長的壓力就會變得很大。
接著查看 gc log,打印 gc log 需要在 JVM 啟動參數(shù)里添加以下參數(shù):
看到的 gc log 形如:

(, 下載次數(shù): 68)
單次 GC 方面并不能直接看出問題,但可以看到 gc 前有很多次 18ms 左右的停頓。
3. 分析和調整

3.1 YGC 頻繁

直接查看 gc log并不直觀,我們可以借用一些可視化工具來幫助我們分析, [gceasy](https://gceasy.io/) 是個挺不錯的網(wǎng)站,我們把 gc log 上傳上去后, gceasy 可以幫助我們生成各個維度的圖表幫助分析。
查看 gceasy 生成的報告,發(fā)現(xiàn)我們服務的 gc 吞吐量是 95%,它指的是 JVM 運行業(yè)務代碼的時長占 JVM 總運行時長的比例,這個比例確實有些低了,運行 100 分鐘就有 5 分鐘在執(zhí)行 gc。幸好這些 GC 中絕大多數(shù)都是 YGC,單次時長可控且分布平均,這使得我們服務還能平穩(wěn)運行。
解決這個問題要么是減少對象的創(chuàng)建,要么就增大 young 區(qū)。前者不是一時半會兒都解決的,需要查找代碼里可能有問題的點,分步優(yōu)化。
而后者雖然改一下配置就行,但以我們對 GC 最直觀的印象來說,增大 young 區(qū),YGC 的時長也會迅速增大。
其實這點不必太過擔心,我們知道 YGC 的耗時是由 GC 標記 + GC 復制 組成的,相對于 GC 復制,GC 標記是非??斓摹6?young 區(qū)內大多數(shù)對象的生命周期都非常短,如果將 young 區(qū)增大一倍,GC 標記的時長會提升一倍,但到 GC 發(fā)生時被標記的對象大部分已經死亡, GC 復制的時長肯定不會提升一倍,所以我們可以放心增大 young 區(qū)大小。
由于低內存舊機器都被換掉了,我把堆大小調整到了 12G,young 區(qū)保留為 8G。
3.2 分代調整

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

(, 下載次數(shù): 81)
我們知道 GC 的提升機制,每次 GC 后,JVM 存活代數(shù)大于 MaxTenuringThreshold 的對象提升到老年代。當然,JVM 還有動態(tài)年齡計算的規(guī)則:按照年齡從小到大對其所占用的大小進行累積,當累積的某個年齡大小超過了 survivor 區(qū)的一半時,取這個年齡和 MaxTenuringThreshold 中更小的一個值,作為新的晉升年齡閾值,但看各代總的內存大小,是達不到 survivor 區(qū)的一半的。

(, 下載次數(shù): 64)
所以這十五個分代內的對象會一直在兩個 survivor 區(qū)之間來回復制,再觀察各分代的平均大小,可以看到,四代以上的對象已經有一半都會保留到老年區(qū)了,所以可以將這些對象直接提升到老年代,以減少對象在兩個 survivor 區(qū)之間復制的性能開銷。
所以我把 MaxTenuringThreshold 的值調整為 4,將存活超過四代的對象直接提升到老年代。
3.3 偏向鎖停頓

還有一個問題是 gc log 里有很多 18ms 左右的停頓,有時候連續(xù)有十多條,雖然每次停頓時長不長,但連續(xù)多次累積的時間也非常可觀。這是因為1.8 之后 JVM 對鎖進行了優(yōu)化,添加了偏向鎖的概念,避免了很多不必要的加鎖操作,但偏向鎖一旦遇到鎖競爭,取消鎖需要進入 safe point,導致 STW。
解決方式很簡單,JVM 啟動參數(shù)里添加 -XX:-UseBiasedLocking 即可。
4. 結果

調整完 JVM 參數(shù)后先是對服務進行壓測,發(fā)現(xiàn)性能確實有提升,也沒有發(fā)生嚴重的 GC 問題,之后再把調整好的配置放到線上機器進行灰度,同時收集 gc log,再次進行分析。
由于 young 區(qū)大小翻倍了,所以 YGC 的頻率減半了,GC 的吞量提升到了 97.75%。 平均 GC 時長略有上升,從 60ms 左右提升到了 66ms,還是挺符合預期的。
由于 CMS 在進行 GC 時也會清理 young 區(qū),CMS 的時長也受到了影響,CMS 的最終標記和并發(fā)清理階段耗時增加了,也比較正常。
另外我還統(tǒng)計了對業(yè)務的影響,之前因為 GC 導致超時的請求大大減少了。
小結

總之,這是一次挺成功的 GC 調整,讓我對 GC 有了更深的理解,但由于沒有深入到 old 區(qū),之前學習到的 CMS 相關的知識還沒有復習到。不過性能優(yōu)化并不是一朝一夕的事,需要時刻關注問題,及時做出調整。
參考:記一次簡單的 JVM 調優(yōu)

-----------------------------




歡迎光臨 愛鋒貝 (http://7gfy2te7.cn/) Powered by Discuz! X3.4