wayne
wayne
发布于 2026-07-31 / 0 阅读
0
0

K8s Pod内存居高不下假象排查与解决(JDK17 G1+Arthas实战)

K8s Pod内存居高不下假象排查与解决(JDK17 G1+Arthas实战)

一、K8s 内存假象

最近有一个服务上线了很多job刷数据任务,服务存在定时批量刷数据、批量Mongo查询、批量序列化任务,每次任务执行后:

  • 业务日志无 OOM、无报错

  • 接口响应正常、GC 频率稳定

  • 但 Lens / K8s 监控 Pod 内存持续高位,长期不下降

很多团队会误判为:内存泄漏、服务异常,进而触发内存告警、误判服务稳定性。

但真实结论:这是 JDK17 G1GC 的经典特性,不是内存泄漏,是「内存不归还操作系统」导致的监控假象。

二、搞懂两个完全不同的内存视角

1. Lens / K8s 监控视角(虚假高位来源)

K8s、Lens、kubectl top、Prometheus 采集的都是:

container_memory_working_set_bytes(进程物理内存 RSS)

代表:操作系统真正分配给 Java 进程的物理内存页。

关键:JVM GC 只能回收堆内对象,不会默认把空闲物理内存还给操作系统

2. Arthas / JVM 视角(真实业务内存)

JVM 内部堆内存分为:

  • 存活对象内存(真正在用)

  • 垃圾对象内存(可被 GC 回收)

  • 空闲堆内存(JVM 持有、未分配、不还给系统)

所以出现经典割裂现象:

  • Arthas 查看 heap used:大幅下降

  • Lens Pod 内存:纹丝不动

3. 为什么 G1 内存还不回去?(JDK17 核心机制)

G1 采用 Region 分块管理内存,内存归还 OS 有两个硬性限制:

  1. 必须是整块 Region 完全空闲,碎片化空闲内存无法释放

  2. JDK17 默认关闭周期性内存归还(G1PeriodicGCInterval 默认 0 禁用)

批量任务场景极易产生堆碎片,导致:堆内明明有空内存,但是无法归还物理内存在使用云平台的容器环境中,这种不利之处特别明显。即使在虚拟机不活动,但如果仍然使用其分配的内存资源,哪怕是其中的一小部分,G1 回收器也仍将保留所有已分配的 Java 堆内存。而这将导致用户需要始终为所有资源付费,哪怕是实际并未用到,而云提供商也无法充分利用其硬件。如果在此期间虚拟机能够检测到 Java 堆内存的实际使用情况,并在利用空闲时间自动将 Java 堆内存返还,则两者都将受益。

三、前置准备:Docker 镜像内置 Arthas

为了随时排查内存问题,不临时拷包、不入侵容器,建议统一在基础镜像预装 arthas-boot.jar。

Dockerfile 集成方案

# 预装 arthas 用于线上内存排查
RUN wget https://arthas.aliyun.com/arthas-boot.jar -O /opt/arthas-boot.jar

优势:

  • 容器任意时刻可快速进入诊断

  • 无需外网、无需上传包

  • 统一线上排查标准工具

进入命令:

java -jar /opt/arthas-boot.jar

四、全套线上内存排查实战命令

以下命令为本次内存问题的精准排查组合拳,可直接作为团队排查规范。

1. 全局堆内存快照分析(定位大对象、Top 内存占用)

vmtool --action heapAnalyze --classNum 5 --objectNum 3

作用:

  • 统计当前堆总对象数、总类数量

  • 展示占用字节最多的类、实例数最多的类

  • 快速定位:byte[]、String、业务实体、集合节点是否异常堆积

典型业务现象:批量刷数据后大量 byte[]/String/MobileLocation 临时对象堆积。

2. 手动强制执行 FullGC(验证是否为垃圾堆积)

vmtool --action forceGc

排查逻辑:

  • GC 后 JVM 堆 used 明显下降 = 只是临时垃圾堆积,无泄漏

  • GC 后内存几乎不变 = 存在常驻对象泄漏

配合 memory 命令可直观对比 Eden/Old 区变化。

3. 溯源大对象引用链(根治内存堆积根源)

vmtool --action referenceAnalyze --className byte[] --objectNum 5 --backtraceNum 3

作用:

  • 分析 byte[] 这类顶级占用对象的上层引用链路

  • 定位是 Mongo 解析、JSON 序列化、IO 报文、缓存集合持有导致的堆积

  • 精准找到代码层面无法回收的源头

五、问题定性:区分「内存假象」和「真内存泄漏」

1. 内存假象(你的线上现状)

  • 定时任务峰值堆拉高,任务结束 JVM 堆可 GC 回落

  • Lens 物理内存不下降,是 G1 不归还内存导致

  • Old 区稳定、无持续上涨、无频繁 FullGC

2. 真实内存泄漏

  • 每次任务结束,Old 区水位逐次抬高

  • GC 后存活对象只增不减

  • 自定义实体类实例数持续上涨不回落

六、最终解决方案:JDK17 G1 最优调优(低风险、最稳定)

不切换 ZGC、不改动 GC 算法、零业务风险,仅开启 G1 周期性内存归还机制。

完整最终生产 JVM 参数

-Xms6g
-Xmx7g
-XX:MetaspaceSize=512m
-XX:MaxMetaspaceSize=1g
-XX:ReservedCodeCacheSize=240m
-XX:+UseG1GC
-XX:MaxGCPauseMillis=400
-XX:G1HeapRegionSize=8M
# 核心:开启空闲周期GC,实现内存归还OS(解决Lens内存虚高)
-XX:G1PeriodicGCInterval=60000
-XX:+G1PeriodicGCInvokesConcurrent
-XX:G1PeriodicGCSystemLoadThreshold=80
-XX:+HeapDumpOnOutOfMemoryError
-XX:+ExitOnOutOfMemoryError

参数解释(适配你的批量任务场景)

  • Xms6g Xmx7g:仅1G浮动空间,峰值扩容OOM风险极低,兼顾稳定性+可归还内存

  • G1PeriodicGCInterval=60000:60秒检测一次空闲堆,减少频繁震荡

  • G1PeriodicGCInvokesConcurrent:周期GC走并发,不STW、不影响业务

  • G1PeriodicGCSystemLoadThreshold=80:CPU繁忙时不回收,避免高峰期抢占资源


评论