Java内存过高需先区分堆内持续增长或非堆异常膨胀:用jstat看Old区回收率、jmap查存活对象、pmap识堆外泄漏、MAT分析Heap Dump定位GC Roots引用链,并结合GC日志验证修复效果。

Java 程序内存占用过高,不能只看 top 里的 RES 值,它混着堆、元空间、直接内存、线程栈等全部物理内存。真正要定位“是哪个区”出问题,得拆开 JVM 内存结构逐层查。
看堆内老年代是否持续堆积
用 jstat -gcutil <pid> 1000 5 观察关键指标:
- O(Old)列:如果长期高于 85% 且 Full GC 后下降极少(比如只降 1~2%),说明老年代对象无法释放,大概率是内存泄漏
- FGC / FGCT:Full GC 频次高、耗时长,但回收效果差,进一步佐证老年代异常
- E(Eden)列:若 Eden 区频繁打满又快速清空(YGC 高频),但对象不断晋升到 Old,说明对象生命周期偏长,需查缓存、集合类使用是否合理
查元空间(Metaspace)是否撑满
元空间存放类定义、方法、常量池等,不属堆内,但会占系统内存:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
-
jstat -gcutil <pid>中 M 列 持续接近 100%,或jcmd <pid> VM.native_memory summary scale=MB显示 metaspace 占用远超预期(如 >512MB),可能是类加载过多 - 常见诱因:热部署频繁(Spring Boot DevTools)、大量动态代理(如 MyBatis、AOP)、OSGi 或微服务中 Jar 包重复加载
- 可加参数
-XX:MaxMetaspaceSize=256m限制上限,并配合-XX:+PrintGCDetails观察是否触发 Metadata GC
识别堆外内存异常膨胀
堆外内存不在 -Xmx 控制范围内,但会反映在进程 RSS 中:
立即学习“Java免费学习笔记(深入)”;
- 用
pmap -x <pid> | grep -E "(anon|libjvm)"查看匿名映射区域大小;若远超堆设置(如 -Xmx2g,但 [anon] 占 3.5g),就存在堆外泄漏 - 典型场景:Netty 的
DirectByteBuffer未清理、JNI 调用未释放 native memory、Log4j2 的异步日志缓冲区失控 - 验证方式:
jcmd <pid> VM.native_memory detail scale=MB,重点看Internal和Other分类是否异常增长
确认线程栈与直接内存占比
这两块虽单个体积小,但数量多时也会显著推高 RSS:
- 线程数过多:用
ps -T -p <pid> | wc -l查线程总数;超过 1000 且持续增长,可能有线程创建未回收(如未关闭的 ExecutorService、漏关的数据库连接) - 直接内存泄漏:检查代码中是否大量使用
ByteBuffer.allocateDirect(),并确认是否调用cleaner或显式释放(尤其 Netty 中ByteBuf.release()忘调) - 可用
jmap -histo:live <pid> | grep -i "bytebuffer\|direct"辅助判断 DirectByteBuffer 实例数是否异常

















