三步走:認識問題,分析問題,解決問題
一、認識問題
【業務背景】某個Java業務應用,所有容器實例minor GC頻繁,major GC偶發,每天重啓一次。
現象是業務每隔30分鐘會從商品域拉取全量商品和SKU數據,更新到緩存中。業務側短期內不好解決這個
二、分析問題
JVM參數配置
【調優前】2GB內存,使用G1GC,最大元數據空間大小是512MB
-Xms2048m -Xmx2048m -XX:MaxMetaspaceSize=512m -XX:ThreadStackSize=256 -XX:+DisableExplicitGC -XX:+UseG1GC
Quick Facts 速覽
I/O Overview 概述
http請求耗時,偶爾較長。
JVM Memory 內存
內存沒有越界,沒看出異常和端倪。
JVM Misc 雜項
Load,1 分鐘負載數據,不好評估影響面。
Log Events,説明日誌寫操作比較多。
JVM Memory Pools (Heap) 內存池(堆內存)
G1 Eden Space 伊甸園與G1 Old Gen 老年代,在2GB堆內存內你爭我搶,蹺蹺板效應。異常情況,我們期望它們是一條筆直的直線,沒有抖動與突刺。
G1 Survivor Space 倖存者空間,使用和提交的空間在38MB之內,從空間大小數據可以看出堆區域大小HeapRegionSize=1MB。
JVM Memory Pools (Non-Heap) 內存池(非堆內存)
Metaspace 元數據空間,使用了200MB不到,最大是512MB。有優化空間
Compressed Class Space 壓縮的類空間,使用了25MB,最大是504MB。有優化空間
Garbage Collection 垃圾收集
Collections 收集次數
end of major GC (Allocation Failure),Full GC,分配失敗,每天最多發生1次;
end of minor GC (G1 Evacuation Pause),Young GC,疏散暫停,發生較頻繁;
end of minor GC (G1 Humongous Allocation),巨型對象分配,發生較頻繁;
end of minor GC (GCLocker Initiated GC)
end of minor GC (Metadata GC Threshold)
Pause Durations 暫停時間
avg end of major GC (Allocation Failure),Full GC,分配失敗,每天最多發生1次,平均耗時在1s左右;
avg end of minor GC (G1 Evacuation Pause),Young GC,疏散暫停,發生較頻繁,平均耗時在100ms以內;
avg end of minor GC (G1 Humongous Allocation),巨型對象分配,發生較頻繁,平均耗時在100ms以內;
max end of major GC (Allocation Failure),Full GC,分配失敗,最大耗時在1s左右;
max end of minor GC (G1 Evacuation Pause),Young GC,疏散暫停,最大耗時在[300,500]ms以內;
max end of minor GC (G1 Humongous Allocation),巨型對象分配,最大耗時在[300,500]ms以內,偶發在[700,1000]ms;
max end of minor GC (GCLocker Initiated GC)
max end of minor GC (Metadata GC Threshold)
Allocated/Promoted 已分配/已升級對象大小
allocated,已分配對象,臨時對象分配較多;
promoted,已升級對象,偶爾升級到老年代;
Classloading 類加載
加載的類數量,沒有一直遞增。一切平靜安好
Buffer Pools 緩衝池
堆外內存,使用量很少,沒有泄露。
容器重啓
容器事件,看到PodOOMKilling,pod was OOM killed.
Pod容器監控,Pod中的filebeat:7.13.4容器內存使用滿了,內存是1GB。
有了上述度量遙測數據,可以針對性地做調優。
大膽假設,小心求證。
綜上所述,有如下幾個疑點:
- minor GC頻繁的根因是臨時對象分配較多,日誌輸出頻繁,引起頻繁的Young GC。 ✅
- major GC偶發的根因是巨型對象分配直接在老年代分配,引起Full GC。 ✅
- 容器重啓的根因是PodOOMKilling,pod was OOM killed.,同一Pod中的
filebeat:7.13.4容器內存1GB使用滿了。 ✅
三、解決問題
大膽假設,小心求證。
【調優後】2GB內存,使用G1GC
最大元數據空間大小是256MB,-XX:MaxMetaspaceSize=256m
堆區域大小HeapRegionSize調整為2MB,-XX:G1HeapRegionSize=2m
最大G1 Eden Space 伊甸園空間調整為1GB,-XX:MaxNewSize=1024m
-Xms2048m -Xmx2048m -XX:MaxNewSize=1024m -XX:MaxMetaspaceSize=256m -XX:ThreadStackSize=256 -XX:+DisableExplicitGC -XX:G1HeapRegionSize=2m -XX:+UseG1GC
filebeat:7.13.4,只是優化了無用日誌,沒有別的動作。無用日誌是罪魁禍首
幾個疑點:
- minor GC頻繁的根因是臨時對象分配較多,日誌輸出頻繁,引起頻繁的Young GC。 ✅
- major GC偶發的根因是巨型對象分配直接在老年代分配,引起Full GC。 ✅
- 容器重啓的根因是PodOOMKilling,pod was OOM killed.,同一Pod中的
filebeat:7.13.4容器內存1GB使用滿了。 ✅
調優後的整體效果
調優前實例:192.168.112.191,過去7天的數據
調優後實例:192.168.106.45,過去1天的數據
【JVM優化對比】收集次數(Collections) 和暫停時間(Pause Durations)
avg end of major GC (Allocation Failure)
【調優前】每天發生一次Full GC
【調優後】沒有發生
avg end of minor GC (G1 Evacuation Pause)
【調優前】平均耗時在[50,70]ms之間,抖動突刺頻繁,偶爾超過100ms
【調優後】平均耗時是40ms,大部分小於50ms,收集頻率是頻繁了
avg end of minor GC (G1 Humongous Allocation)
【調優前】大對象分配失敗頻繁,平均耗時在[50,70]ms之間,Region大小是1MB
【調優後】平均耗時是40ms,收集頻率是偶爾,Region大小是2MB
max end of major GC (Allocation Failure)
【調優前】分配失敗,引起Full GC
【調優後】沒有發生
max end of minor GC (G1 Evacuation Pause)
【調優前】平均耗時波動較大,經常[200,300]ms,偶爾耗時較高,超過500ms
【調優後】偶爾耗時較高,超過500ms
max end of minor GC (G1 Humongous Allocation)
【調優前】收集頻繁,平均耗時波動較大,經常[150,300]ms
【調優後】平均耗時是40ms,收集頻率是偶爾
【JVM優化對比】已分配/已升級對象大小(Allocated/Promoted)
allocated
【調優前】臨時對象分配較多
【調優後】臨時對象分配很多,是根因
promoted
【調優前】偶爾升級到老年代
【調優後】偶爾升級到老年代
參考引用
- JVM調優實戰:to-space exhausted & Evacuation Failure
- JVM GC 調優方法論與系統總結
- JVM--淺談G1收集器
- GC日誌解讀,這次別再説看不懂GC日誌了
- JVM GC日誌詳細分析,ParallelGC和G1
- 深入理解 JVM 的垃圾收集器:CMS、G1、ZGC
- G1GC深度探索--Young gc耗時持續增長原因分析
工欲善其事,必先利其器。
- HeapDump - Java內存Dump分析
- Memory Analyzer (MAT) - Eclipse
- HeapHero