k8s-pod部署java应用,jvm内存正常,但是pod内存不足oom排查
目录
- 目的
- 步骤
- 进入k8s-pod终端
- 通过 top 查看 pod 内存使用情况
- 通过 ps 查看 jvm 参数
- 通过 jstat 查看堆内存
- 查询直接内存使用情况
- 查询线程内存使用情况
- 统计真实线程数
- 定位线程泄漏的来源
- 按线程状态分类
- 按线程名称前缀分类
- 查看具体线程堆
- 验证线程泄露原因
- 通过代码检查验证
目的
k8s-pod部署java应用,jvm内存正常,但是pod内存不足oom排查
步骤
进入k8s-pod终端
通过 kubectl 进入 pod 或者直接操作 pod 命令。
通过 top 查看 pod 内存使用情况
可以直接在 pod 中执行 top 命令,或者通过 kubectl top pod <你的pod名称> 的方式执行。
top - 17:08:40 up 43 days, 23:39, 0 users, load average: 1.21, 1.16, 1.17
Tasks: 7 total, 1 running, 6 sleeping, 0 stopped, 0 zombie
%Cpu(s): 6.9 us, 0.5 sy, 0.0 ni, 91.9 id, 0.0 wa, 0.6 hi, 0.1 si, 0.0 st
MiB Mem : 63003.8 total, 12938.7 free, 19556.6 used, 30508.4 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 42743.2 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 7 admin 20 0 30.7g 7.6g 29580 S 9.3 12.4 364:25.59 java
1223533 admin 20 0 21280 4540 3844 R 0.3 0.0 0:00.02 top 1 admin 20 0 16696 3400 3140 S 0.0 0.0 0:00.00 sh
1213439 admin 20 0 16696 3332 3072 S 0.0 0.0 0:00.00 sh
1213445 admin 20 0 16696 276 0 S 0.0 0.0 0:00.00 sh
1213446 admin 20 0 15900 2408 2252 S 0.0 0.0 0:00.00 script
1213448 admin 20 0 16936 3888 3440 S 0.0 0.0 0:00.00 bash
可以看到 java 进程总共使用内存 7.6g,而 java 进程的总内存占用 = 堆内存(-Xmx控制) + 非堆内存(元空间、直接内存、线程栈、JVM 自身开销等)。
接下来排查非堆内存和 jvm 堆内存各自占用多少。
通过 ps 查看 jvm 参数
ps -ef | grep java
admin 7 1 5 10月04 ? 06:04:35 java -server -Xms4g -Xmx7g -Xmn2356m -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -Dfile.encoding=utf-8 -Djasypt.encryptor.password=xxxx -Djava.security.egd=file:/dev/./urandom -jar /home/admin/application/xxxx/bootstrap.jar --server.port=8080 --spring.profiles.active=xxxx
通过 jstat 查看堆内存
jstat -gc 7 1000 3S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
241216.0 241216.0 0.0 31466.1 1930112.0 349175.8 2201600.0 1772567.1 226328.0 213438.7 26928.0 24826.2 613 7.113 32 2.237 9.350
241216.0 241216.0 0.0 31466.1 1930112.0 354020.2 2201600.0 1772567.1 226328.0 213438.7 26928.0 24826.2 613 7.113 32 2.237 9.350
241216.0 241216.0 0.0 31466.1 1930112.0 354180.6 2201600.0 1772567.1 226328.0 213438.7 26928.0 24826.2 613 7.113 32 2.237 9.350
从jstat -gc的输出结果来看,当前堆内存的实际使用量远低于-Xmx7g的上限。
Java 进程总物理内存约 7.6g,减去堆实际使用的 2.01g,非堆内存占用约 5.59g。
非堆内存主要包括:元空间(Metaspace)、直接内存(Direct Memory)、线程栈、JVM 自身开销等。其中
- 元空间(MU):213438.7 KB ≈ 0.203g(正常,未超限)
- 压缩类空间(CCSU):24826.2 KB ≈ 0.023g(正常)
因此,超限的非堆内存大概率来自直接内存或线程栈,接下来需要重点排查。
查询直接内存使用情况
直接内存用于 NIO 操作(如 Netty、文件 IO),默认无上限(与堆上限一致,即 7g),是最可能超限的部分。
[application]$ cat /proc/7/cmdline | tr '\0' ' ' | grep MaxDirectMemorySize // 确认是否有直接内存限制
[application]$ jcmd 7 VM.native_memory summary | grep "Direct" // 查看直接内存实际使用,若支持,直接显示直接内存使用
Picked up JAVA_TOOL_OPTIONS: -javaagent:/home/admin/.opt/AliyunJavaAgent/aliyun-java-agent.jar...
[application]$ grep -E "anon_inode|java.nio" /proc/7/maps | awk '{print $2}' | grep -v '-' | awk '{sum += $1} END {print sum/1024/1024 " MB"}' // 若jcmd不支持,用内存映射文件间接判断
0 MB
通过grep -E “anon_inode|java.nio” /proc/7/maps计算直接内存约为 0MB,结合jcmd无结果,说明直接内存不是非堆超限的原因。
查询线程内存使用情况
排除直接内存和线程栈后,剩余的非堆内存(约 5.59g)可能来自:
- JVM Code Cache(存储即时编译的代码,默认上限约 240MB,但可能因配置异常增大)
- GC 相关内存(如标记 - 清除算法的内存开销、GC 日志缓冲区等)
- 第三方 Agent 内存(你的启动参数包含 AliyunJavaAgent、MSE 等 Agent,可能存在内存泄漏或过度占用)
- 本地内存泄漏(如 JNI 调用分配的 C 堆内存未释放,JVM 无法跟踪)
统计真实线程数
[application]$ jstack 7 | grep -c "tid=" // 统计所有包含"tid="的线程(jstack中线程的标准标识)
Picked up JAVA_TOOL_OPTIONS: -javaagent:/home/admin/.opt/AliyunJavaAgent/aliyun-java-agent.jar...
12825
[application]$ ps -T -p 7 | wc -l // 直接查看进程的线程总数(操作系统层面)
12862
数量完全超出正常 Java 应用的线程范围(正常 Web 应用通常几百到几千个线程)。
jvm 未显式设置-Xss(线程栈大小),默认值通常为 1MB / 线程(不同 JDK 版本略有差异)。
总线程栈内存占用 ≈ 12861 线程 × 1MB / 线程 ≈ 12.56GB,这已经远超 pod 的 8GB 内存限制!
之前计算的 “非堆内存约 5.59g”,本质就是线程栈的大量占用(因部分线程可能未完全初始化或栈未占满,所以实际占用低于 12.56GB,但依然远超安全阈值)。
下一步需要定位线程泄漏的来源。
定位线程泄漏的来源
按线程状态分类
jstack 7 | grep -E "java.lang.Thread.State: (RUNNABLE|WAITING|TIMED_WAITING|BLOCKED)" | awk '{print $NF}' | sort | uniq -c | sort -nr11328 RUNNABLE749 (parking)714 (sleeping)16 monitor)
按线程名称前缀分类
jstack 7 | grep -E '"[^"]+" #' | awk -F'"' '{print $2}' | awk '{print substr($0,1,20)}' | sort | uniq -c | sort -nr | head -n 1011152 httpclient-dispatch-697 idle-connection-evic200 DubboServerHandler-1122 com.alibaba.nacos.cl70 aliyun-log-producer-62 172024109152_944709_49 nacos-grpc-client-ex48 NettyClientPublicExe40 ConsumeMessageThread16 NettyServerWorker-8-
查看具体线程堆
jstack 7 | awk -v RS='' '/httpclient-dispatch-/' | head -n 2"httpclient-dispatch-16" #1227473 daemon prio=5 os_prio=0 tid=0x00007f25282cc800 nid=0x12bafc runnable [0x00007f20af736000]java.lang.Thread.State: RUNNABLEat sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)- locked <0x00000006851e91a0> (a sun.nio.ch.Util$3)- locked <0x00000006851e9190> (a java.util.Collections$UnmodifiableSet)- locked <0x00000006851e91b0> (a sun.nio.ch.EPollSelectorImpl)at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)at com.aliyun.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreIOReactor.java:113)at com.aliyun.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(AbstractSingleCoreIOReactor.java:86)at com.aliyun.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44)at java.lang.Thread.run(Thread.java:748)
"httpclient-dispatch-15" #1227472 daemon prio=5 os_prio=0 tid=0x00007f25282cb000 nid=0x12bafb runnable [0x00007f20af534000]java.lang.Thread.State: RUNNABLEat sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)- locked <0x000000068549d170> (a sun.nio.ch.Util$3)- locked <0x000000068549d160> (a java.util.Collections$UnmodifiableSet)- locked <0x000000068549d180> (a sun.nio.ch.EPollSelectorImpl)at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)at com.aliyun.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreIOReactor.java:113)at com.aliyun.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(AbstractSingleCoreIOReactor.java:86)at com.aliyun.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44)at java.lang.Thread.run(Thread.java:748)
从线程信息推测应用使用的阿里云 Apache HttpClient(aliyun.apache.hc.core5)存在严重线程泄漏。下一步需要验证推测是否正确
验证线程泄露原因
通过代码检查验证
检查代码是否存在新建 HttpClient 实例,且未正确关闭的场景。