监控jvm内存需区分堆内/堆外、各代区域,结合gc日志与对象分布综合判断;用jstat、jcmd等轻量命令实时排查,jmap+mat深度分析泄漏,长期依赖gc日志和jmx趋势监控,并警惕堆外oom。

监控与分析 JVM 内存使用,关键在于“看得到、分得清、判得准、调得对”。不能只盯着堆内存总量,而要区分堆内/堆外、年轻代/老年代/元空间,并结合 GC 行为和对象分布综合判断。
实时查看内存分布与 GC 状态
用命令行工具快速掌握运行时状态,适合生产环境轻量排查:
-
jstat -gc
:查看各代内存容量(S0C/S1C/EC/OC/MC)和使用量(S0U/S1U/EU/OU/MU),以及 YGC/FGC 次数和耗时; -
jstat -gcutil
:更直观地看到 Eden、老年代、元空间的使用率百分比(E/O/M 列),便于发现持续上涨趋势; -
jcmd
VM.native_memory summary :启用 NMT(-XX:NativeMemoryTracking=summary)后,可查堆外内存占用,如 Thread(线程栈)、Code(JIT 代码)、GC(GC 元数据)等模块的 committed 大小; -
jmap -histo:live
:统计当前活跃对象数量与总大小,快速定位高频类(如 HashMap、String、byte[])是否异常膨胀。
捕获内存快照做深度分析
当怀疑内存泄漏或大对象堆积时,需导出堆转储并离线分析:
- 用 jmap -dump:format=b,file=heap.hprof
生成堆快照(建议加 live 参数避免冗余); - 用 Eclipse MAT 打开后查看 “Leak Suspects” 报告,或用 “Dominator Tree” 找出直接占用内存最多的对象及其引用链;
- 关注 “Retained Heap” 而非仅 “Shallow Heap”,因为真正拖垮内存的是对象及其所有可达子对象的总和;
- 若发现大量相同类实例但无明显业务逻辑支撑,需检查缓存未清理、监听器未注销、静态集合未清除等典型泄漏点。
长期监控与趋势研判
单次快照只能反映瞬时状态,稳定运行依赖持续可观测性:
- 启动时添加 GC 日志参数:-Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseGCLogFileRotation,配合 GCEasy 或 GCViewer 分析停顿时间、晋升速率、GC overhead(>20% 需警惕);
- 通过 JMX 暴露指标(如 java.lang:type=MemoryPool 中各 MemoryPool 的 Usage.used / Usage.max),用 Prometheus + Grafana 绘制 Eden 使用率、老年代水位、元空间增长曲线;
- 重点关注三个信号:老年代使用率缓慢但持续上升、Full GC 频率从几小时一次变为几分钟一次、元空间 OOM 前出现频繁的 Metaspace GC;
- Arthas 的 memory 命令支持在线查看堆内各区域使用、实时触发 dump、甚至按类名统计实例数,无需重启应用。
区分堆内与堆外内存问题
OOM 不一定发生在堆里,堆外内存失控同样致命:
- 堆内 OOM(java.lang.OutOfMemoryError: Java heap space):典型表现为老年代满且 Full GC 后仍无法释放;
- 元空间 OOM(java.lang.OutOfMemoryError: Metaspace):多见于动态生成类场景(如 Spring Boot DevTools、Groovy 脚本、大量反射代理);
- 堆外 OOM(java.lang.OutOfMemoryError: Direct buffer memory):NIO ByteBuffer.allocateDirect() 过多且未 clean,或 Netty PooledByteBufAllocator 配置不当;
- 本地内存耗尽(如 java.lang.OutOfMemoryError: unable to create new native thread):往往因线程数超限(ThreadStackSize × 线程数 > 物理内存),需查 jstack 线程数及状态。











