一、事件背景
今天群里突然出现告警:某核心接口偶发性响应失败,报错信息为 finishConnect() failed: No route to host: xxx.18.108.xxx:8888
。初步排查发现,Kubernetes监控到某个Pod内存占用超过80%,触发自动扩容(HPA)。内存回落后,HPA执行缩容,缩容过程中Pod下线,但此时仍有流量被调度到该Pod,导致请求失败。
然而,更关键的问题是:为何Pod内存占用会突然飙升到80%?
二、问题排查过程
1. 去pod上dump出堆转存储文件
perl
jmap -dump:live,format=b,file=/tmp/heap.hprof <pid>
2. 分析Heap Dump文件
工具:VisualVM
步骤1:加载Dump文件
bash
jhat heap.hprof # 或使用MAT直接导入
步骤2:检查内存分布
- Dominator Tree:按Retained Heap排序。
发现最大的两个对线,一个是IPUtil,60M左右,用来保存ip池信息,这个是正常占用。
另一个是spring的切面的,30多兆,也是正常的。
步骤3:检查GC Root引用链
- 对老年代中占比较大的对象(如
byte[]
)执行 Merge Shortest Paths to GC Roots,未发现异常静态引用。
结论 :堆内存无泄漏 ,但内存占用矛盾需进一步分析。
另外: 监控显示应用内存使用率80%, 已经使用1.7G,但是dump出来的文件只有600M, 而jmap -dump:live
命令执行转存储前会执行一次Full GC
, 说明老年代的垃圾能正常被清理,也就说明没有内存泄露, 那问题是什么呢?
3. 检查JVM参数配置
发现某次上线调整了JVM参数:
调大了堆内存大小,但是没有修改年轻代大小, 年轻代依然是256M,这样就会导致年轻代过小。
关键问题:
- 新生代过小 :默认
-XX:MaxNewSize=256m
,导致Young GC时存活对象极易晋升老年代。 - 晋升策略失衡:老年代快速填充,但未达到2GB上限,触发监控阈值(80%即1.6GB)。
三、根本原因
-
JVM参数配置错误:
- 最大堆内存
-Xmx2048m
,新生代-XX:MaxNewSize=256m
。 - 新生代过小,对象快速晋升老年代,老年代占用迅速达到1.6GB(80%阈值)。
- 最大堆内存
-
HPA机制副作用:
- 内存阈值触发扩容,缩容时Pod下线导致流量异常。
四、总结
本次问题暴露了JVM参数配置不当与监控粗粒度带来的风险。核心教训:
- jvm指标改动应兼顾各个配置,而不是单独简单增大内存。
- 内存监控需细化,区分堆内外内存。
- HPA策略需结合业务场景,避免自动化机制副作用。
通过参数调优与监控完善,系统内存占用趋于平稳,类似问题未再发生。
附录:关键排查命令
bash
# 生成Heap Dump
jmap -dump:live,format=b,file=heap.hprof <PID>
# 实时监控GC
jstat -gcutil <PID> 1000
# 分析Native内存
jcmd <PID> VM.native_memory summary
希望此文能为类似问题的排查提供参考。