一次簡單的 JVM 調優(yōu),性能提升了15%
點擊關注公眾號,Java干貨及時送達
來源:https://zhenbianshu.github.io/
背景
最近對負責的項目進行了一次性能優(yōu)化,其中包括對 JVM 參數的調整,算是進行了一次簡單的 JVM 調優(yōu),JVM 參數調整之后,服務的整體性能有 15% 左右的提升,還算不錯。
舊狀
首先是查看當前 GC 的情況,主要是使用 jstat 查看 GC 的概況,再查看 gc log,分析單次 gc 的詳細狀況。
使用 jstat -gcutil pid 1000 每隔一秒打印一次 gc 統(tǒng)計信息。

可以看到,單次 gc 平均耗時是 60ms 左右,還算可以接受,但 YGC 非常頻繁,基本上每秒一次,有的時候還會一秒兩次,在一秒兩次的時候,服務對業(yè)務響應時長的壓力就會變得很大。
接著查看 gc log,打印 gc log 需要在 JVM 啟動參數里添加以下參數:
-XX:+PrintGCDateStamps:打印 gc 發(fā)生的時間戳。-XX:+PrintTenuringDistribution:打印 gc 發(fā)生時的分代信息。-XX:+PrintGCApplicationStoppedTime:打印 gc 停頓時長-XX:+PrintGCApplicationConcurrentTime:打印 gc 間隔的服務運行時長-XX:+PrintGCDetails:打印 gc 詳情,包括 gc 前/內存等。-Xloggc:../gclogs/gc.log.date:指定 gc log 的路徑
看到的 gc log 形如:

單次 GC 方面并不能直接看出問題,但可以看到 gc 前有很多次 18ms 左右的停頓。
分析和調整
YGC 頻繁
直接查看 gc log 并不直觀,我們可以借用一些可視化工具來幫助我們分析, [gceasy](https://gceasy.io/) 是個挺不錯的網站,我們把 gc log 上傳上去后, gceasy 可以幫助我們生成各個維度的圖表幫助分析。
解決這個問題要么是減少對象的創(chuàng)建,要么就增大 young 區(qū)。前者不是一時半會兒都解決的,需要查找代碼里可能有問題的點,分步優(yōu)化。
而后者雖然改一下配置就行,但以我們對 GC 最直觀的印象來說,增大 young 區(qū),YGC 的時長也會迅速增大。46 張 PPT 弄懂 JVM 這個必須分享給你。
其實這點不必太過擔心,我們知道 YGC 的耗時是由 GC 標記 + GC 復制 組成的,相對于 GC 復制,GC 標記是非??斓?。而 young 區(qū)內大多數對象的生命周期都非常短,如果將 young 區(qū)增大一倍,GC 標記的時長會提升一倍,但到 GC 發(fā)生時被標記的對象大部分已經死亡, GC 復制的時長肯定不會提升一倍,所以我們可以放心增大 young 區(qū)大小。
由于低內存舊機器都被換掉了,我把堆大小調整到了 12G,young 區(qū)保留為 8G。Java 系列教程及源碼整理:https://github.com/javastacks/javastack
分代調整
除了 GC 太頻繁之外,GC 后各分代的平均大小也需要調整。

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

所以這十五個分代內的對象會一直在兩個 survivor 區(qū)之間來回復制,再觀察各分代的平均大小,可以看到,四代以上的對象已經有一半都會保留到老年區(qū)了,所以可以將這些對象直接提升到老年代,以減少對象在兩個 survivor 區(qū)之間復制的性能開銷。
所以我把 MaxTenuringThreshold 的值調整為 4,將存活超過四代的對象直接提升到老年代。46 張 PPT 弄懂 JVM 這個必須分享給你。
偏向鎖停頓
還有一個問題是 gc log 里有很多 18ms 左右的停頓,有時候連續(xù)有十多條,雖然每次停頓時長不長,但連續(xù)多次累積的時間也非??捎^。
1.8 之后 JVM 對鎖進行了優(yōu)化,添加了偏向鎖的概念,避免了很多不必要的加鎖操作,但偏向鎖一旦遇到鎖競爭,取消鎖需要進入 safe point,導致 STW。
解決方式很簡單,JVM 啟動參數里添加 -XX:-UseBiasedLocking 即可。另外,關注公眾號Java技術棧,在后臺回復:面試,可以獲取我整理的 JVM 系列面試題和答案,非常齊全。
結果
調整完 JVM 參數后先是對服務進行壓測,發(fā)現性能確實有提升,也沒有發(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)化并不是一朝一夕的事,需要時刻關注問題,及時做出調整。






關注Java技術棧看更多干貨


