三步走:认识问题,分析问题,解决问题
一、认识问题
【业务背景】某个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耗时持续增长原因分析
工欲善其事,必先利其器。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。