应从日常监控识别内存异常:堆长期高位僵硬波动、Old Gen缓升伴Full GC增多、Eden回收后仍超90%、Metaspace单向增长超80%、RES显著大于堆与非堆之和、GC日志现晋升失败/Real远超User+System/老代回收无效/Young GC过频。

Java 应用内存分配不合理,往往不会立刻崩溃,但会在监控中留下清晰痕迹。关键不是等 OOM 发生,而是从日常指标中识别“不自然”的内存行为。
看堆内存使用率是否长期高位且波动僵硬
健康应用的堆内存使用曲线应有节奏:请求增多时上升、GC 后明显回落、空闲时稳定在较低水位。如果监控显示堆内存长期卡在 85% 以上,且回落幅度极小(比如一次 Minor GC 只降 2~3%),说明新生代可能过小、对象晋升太快,或老年代已接近饱和。
- 重点关注 GC 日志中 Old Gen 使用量持续缓慢爬升,且 Full GC 频率增加
- 对比 Eden 区每次回收前后大小:若回收后仍占满 90%+,说明对象创建速率远超回收能力,可能是流量突增,也可能是内存配置没跟上业务规模
查非堆内存(Metaspace)是否持续增长不回落
Metaspace 存储类元数据,正常情况下加载完类后趋于稳定。若监控发现 Metaspace 使用率随时间单向上涨,且 GC 后几乎不释放,大概率存在类加载器泄漏——比如热部署未清理旧 ClassLoader、动态生成类(如 CGLIB、JSON 序列化框架)未限制数量。
- Metaspace > 80% 持续占用是危险信号,JDK 8+ 不再有永久代溢出,但 Metaspace OOM 一样会抛
java.lang.OutOfMemoryError: Metaspace - 配合
jstat -gc <pid>观察MU(Metaspace Used)与MC(Metaspace Capacity)比值,长期 > 0.9 就该介入
比进程 RES 内存和 JVM 堆内存差值是否过大
JVM 进程的 RES(常驻内存)≠ 堆内存(-Xmx)。若监控显示 RES 稳定在 6GB,而堆最大才 4.8G,且非堆加起来不到 1G,那多出来的 1GB+ 很可能来自堆外内存:DirectByteBuffer、NIO 缓冲区、JNI 调用、线程栈累积等。
立即学习“Java免费学习笔记(深入)”;
- 常见诱因:大量使用
ByteBuffer.allocateDirect()但未显式clean();线程数过多(每个线程默认栈 1MB);Log4j2 异步日志的 RingBuffer 占用 - 用
pmap -x <pid>或cat /proc/<pid>/smaps | grep -E "(Rss|MMAP|Direct)"定位大块匿名内存区域
盯 GC 日志里的关键模式是否异常
启用 -Xloggc:gc.log -XX:+PrintGCDetails 后,不要只看“有没有 GC”,而要看四类信号:
-
晋升失败(Promotion Failed):日志出现
[GC (Promotion Failure)],说明 Survivor 放不下、老年代又没空间接收,直接触发 Full GC——典型配置失衡 - Real Time 明显大于 User + System:比如 Real=300ms,User+System=80ms,差值大说明系统在交换(swap)或严重缺页,物理内存真不够了
-
老年代回收后空间未释放:
Tenured: 120000K->119800K,几乎没动,说明对象被强引用滞留,可能泄漏 - Young GC 频率过高:10 秒内发生多次 Minor GC,但 Eden 回收效果差,指向新生代太小或对象生命周期偏长


















