close

(給ImportNew加星標,提高Java技能)

一、前言

JVM 性能優化步驟:

預估系統參數

壓測後,調整 JVM 參數

線上系統監控和優化

統一的 JVM 參數模板

線上頻繁 Full GC 的表現:

機器 CPU 負載過高

頻繁 Full GC 報警

系統無法處理請求或者處理過慢

頻繁 Full GC 常見原因:

對象頻繁進入老年代,頻繁觸發 Full GC

系統承載高並發請求,或處理數據量過大,導致 Young GC 頻繁,每次 Young GC 過後存活對象太多,內存分配不合理,Survivor 區域過小。

系統一次性加載過多數據進入內存,大對象直接入老年代,頻繁觸發 Full GC

內存泄漏,對象無法回收,一直占用在老年代裡,頻繁觸發 Full GC

MetaSpace (永久代)加載類過多,觸發 Full GC

代碼中使用 System.gc(),觸發 Full GC

針對以上 Full GC 常見的原因,對應的優化方式:

jstat 分析,合理分配內存,調大 Survivor 區域

dump 出內存快照,用 MAT 工具進行分析,代碼上排查

dump 出內存快照,用 MAT 工具進行分析,代碼上排查

若內存使用不多,還頻繁觸發 Full GC,那麼優化加載的類

若內存使用不多,還頻繁觸發 Full GC,代碼上排查,刪除 System.gc()

一、案例一:高分配速率(High Allocation Rate)

分配速率(Allocation rate)表示單位時間內分配的內存量。

通常使用 MB/sec 作為單位。上一次垃圾收集之後, 與下一次 GC 開始之前的年輕代使用量, 兩者的差值除以時間, 就是分配速率。分配速率過高就會嚴重影響程序的性能, 在 JVM 中可能會導致巨大的 GC 開銷。

正常系統: 分配速率較低 ~ 回收速率 -> 健康

內存泄漏: 分配速率 持續大於 回收速率 -> OOM

性能劣化: 分配速率較高 ~ 回收速率 -> 亞健康

JVM 啟動之後 291 ms, 共創建了 33,280 KB 的對象。第一次 Minor GC(小型GC) 完成後, 年輕代中還有 5,088 KB 的對象存活。

在啟動之後 446 ms, 年輕代的使用量增加到 38,368 KB , 觸發第二次 GC, 完成後年輕代的使用量減少到 5,120 KB。

在啟動之後 829 ms, 年輕代的使用量為 71,680 KB, GC 後變為 5,120 KB。

思考一個問題, 分配速率, 到底影響什麼?

想一想, new 出來的對象, 在什麼地方。

答案就是, Eden。

假如我們增加 Eden, 會怎麼樣。考慮蓄水池效應。最終的效果是, 影響 Minor GC 的次數和時間, 進而影響吞吐量。

在某些情況下, 只要增加年輕代的大小, 即可降低分配速率過高所造成的影響。

增加年輕代空間並不會降低分配速率, 但是會減少 GC 的頻率。如果每次 GC 後只有少量對象存活, minor GC 的暫停時間就不會明顯增加。

二、案例二:過早提升(Premature Promotion)

提升速率(promotion rate)用于衡量單位時間內從年輕代提升到老年代的數據量。

一般使用 MB/sec 作為單位, 和分配速率類似。

JVM 會將長時間存活的對象從年輕代提升到老年代。根據分代假設, 可能存在一種情況, 老年代中不僅有存活時間長的對象, 也可能有存活時間短的對象。

這就是過早提升: 對象存活時間還不夠長的時候就被提升到了老年代。

major GC 不是為頻繁回收而設計的, 但 major GC 現在也要清理這些生命短暫的對象, 就會導致 GC 暫停時間過長。這會嚴重影響系統的吞吐量。

GC 之前和之後的年輕代使用量以及堆內存使用量。

這樣就可以通過差值算出老年代的使用量。

和分配速率一樣, 提升速率也會影響 GC 暫停的頻率。但分配速率主要影響 minor GC, 而提升速率則影響 major GC 的頻率。

有大量的對象提升, 自然很快將老年代填滿。老年代填充的越快, 則 major GC 事件的頻率就會越高。

一般來說過早提升的症狀表現為以下形式:

短時間內頻繁地執行 full GC

每次 full GC 後老年代的使用率都很低, 在 10-20% 或以下

提升速率接近於分配速率

要演示這種情況稍微有點麻煩, 所以我們使用特殊手段, 讓對象提升到老年代的年齡比默認情況小很多。指定 GC 參數 -Xmx24m -XX:NewSize=16m -XX:MaxTenuringThreshold=1, 運行程序之後, 可以看到下面的 GC 日誌:

解決這類問題, 需要讓年輕代存放得下暫存的數據, 有兩種簡單的方法:

增加年輕代的大小, 設置 JVM 啟動參數, 類似這樣: -Xmx64m -XX:NewSize=32m, 程序在執行時, Full GC 的次數自然會減少很多, 只會對 minor GC 的持續時間產生影響。

減少每次批處理的數量, 也能得到類似的結果。至於選用哪個方案, 要根據業務需求決定。在某些情況下, 業務邏輯不允許減少批處理的數量, 那就只能增加堆內存, 或者重新指定年輕代的大小。如果都不可行, 就只能優化數據結構, 減少內存消耗。

但總體目標依然是一致的: 讓臨時數據能夠在年輕代存放得下。

轉自:https://juejin.cn/post/7135467909023236109

鏈接:格格步入

- EOF -

推薦閱讀點擊標題可跳轉

面試官:線程崩了,為什麼不會導致 JVM 崩潰呢?如果是主線程呢?

後端必備:如何排查問題以及 JVM 調優思路

看完本文有收穫?請轉發分享給更多人

關注「ImportNew」,提升Java技能

點讚和在看就是最大的支持❤️

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()