题目:在什么情况下会发生OOM?都有哪些类型的OOM?都如何解决和监控?
难度:high
标签:jvm/oom, general/knowledge, difficulty/high
评分:⭐⭐⭐☆☆(3/5)
OOM 的本质就是内存对象无法再分配,原有的内存已经被占满了。 Out of memory error 它本身无法再分配内存对象。一种是内存泄露,所有的堆空间都被占满了,垃圾回收也回收不到,然后达到一定百分比,具体是多少没有印象了,应该有个百分比,并不是 100%。另外有方法区,方法区如果占满了,也会导致 OOM,占溢出。我印象中主要是这 3 种 OOM 产生方式,具体类型就是堆内存溢出、占溢出。和方法区溢出,也就是元空间溢出,对象的一存溢出。监控的话,监控内存使用情况,然后以及监控 GC 的次数频率,然后 GC 回收的比例。通过这些去监控具体的 OOM 的可能情况。内存比例如果是线性增长的话,没有被回收掉,每次是线性增长,就需要关注具体哪些对象产生了问题。然后去对应的监控,找到对应的对象进行分析处理,看是什么原因导致这些对象无法被回收,是内存出、方法区溢出,还是具体的哪些定义了大量的 class,比如说 class 加载,大量的那个 class 类加载导致内存溢出。哦,根据不同情况,因为大量 class 类加载基本上是反射这些东西用的导致的,就这样处理。然后内存溢出的话,是否是有不合理的对象循环创建?然后对应的进行调整。
优点:
-XX:+HeapDumpOnOutOfMemoryError 的触发阈值(JVM 默认在接近堆上限而非满时触发,通常是老年代使用率超阈值)不足:
-XX:MaxMetaspaceSize 满导致,通常因 ClassLoader 泄漏、Groovy/脚本引擎、反射动态代理生成大量类-XX:+HeapDumpOnOutOfMemoryError 这个救命参数——OOM 时自动 dump 堆,是生产上最重要的排查手段System.gc() 避免 OOM补充:
-XX:MaxDirectMemorySize 限制)-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path(救命参数)-Xlog:gc*(JDK 9+)或 -XX:+PrintGCDetailsdashboard、Prometheus + JVMExporterjcmd <pid> GC.heap_info、jcmd <pid> VM.native_memory(NMT)-Xmx、优化对象生命周期、用 WeakReference/Cache(Caffeine)、连接池设上限-XX:MaxMetaspaceSize、排查 ClassLoader 泄漏、避免动态代理过多-XX:MaxDirectMemorySize核心考点定位:考察 OOM 的分类完整性+每种类型的触发场景+监控和解决手段。
知识点拆解:
java.lang.OutOfMemoryError 时,JVM 不会立即退出(除非是特定类型或 GC 前最后一次尝试失败)| 类型 | 消息 | 触发场景 |
|---|---|---|
| Java heap space | 堆溢出 | 缓存无界、集合未清、监听器未反注册 |
| GC overhead limit exceeded | GC >98% 但回收 <2% | 内存接近满但 GC 无法有效回收 |
| Metaspace | 元空间溢出(JDK 8+) | 动态代理、CGLIB、Groovy 脚本、ClassLoader 泄漏 |
| Direct buffer memory | 直接内存溢出 | NIO/Netty 未释放 DirectByteBuffer |
| unable to create new native thread | 线程过多 | Thread 未复用、线程池无上限 |
| Requested array size exceeds VM limit | 数组过大 | 一次申请超大数组 |
| Compressed class space | 压缩类空间满 | -XX:CompressedClassSpaceSize 满 |
| unable to make continuation | Loom 协程溢出 | Java 21+ 虚拟线程相关 |
static Map、static List 无清理)readAllBytes、readLines)ByteBuf 未 release()FileChannel.map() 未 unmapByteBuffer.allocateDirect 未 GC 回收(JDK 8 需 PhantomReference 手动清理)Thread.start() 无上限-XX:+HeapDumpOnOutOfMemoryError:OOM 时自动 dump(救命参数!)-XX:HeapDumpPath=/var/log/dumps/:dump 路径-XX:+ExitOnOutOfMemoryError:OOM 后退出,方便容器重启-Xlog:gc*(JDK 9+)或 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps-XX:NativeMemoryTracking=detail(NMT)jstat -gcutil <pid> 1000:GC 频率与堆使用率jcmd <pid> GC.heap_info:堆信息jcmd <pid> VM.native_memory summary:NMT 原生内存jmap -histo:live <pid>:对象直方图dashboard| 问题 | 短期处置 | 长期解决 |
|---|---|---|
| 堆溢出 | kill -9 + 换实例、扩堆 | 修内存泄漏、优化分配 |
| Metaspace 溢出 | 调大 -XX:MaxMetaspaceSize | 排查 ClassLoader 泄漏 |
| DirectBuffer 溢出 | kill + 重启 | 显式释放、JDK 9+ Cleaner |
| GC overhead | 扩堆、换 GC 器 | 修泄漏、优分配 |
| 线程过多 | kill 线程、扩容 | 线程池化、ThreadLocal 管理 |
java.lang.OutOfMemoryError: Direct buffer memoryjcmd <pid> VM.native_memory summary 看 DirectByteBuffer 增长ByteBuf.release() 是否被调用、ReferenceCountUtil.safeReleaseByteBuf.refCnt() 检查,或改用 ReferenceCounted 自动清理考察方向:
延伸递进面试题:
jcmd <pid> GC.class_histogram 看 ClassLoader 数量;查热部署、脚本引擎、反射动态代理;用 VM.classloader_stats 事件jcmd VM.native_memory、JFR jdk.DirectByteBuffer 事件;Netty 用 ByteBuf.refCnt()-XX:+HeapDumpOnOutOfMemoryError 保证 dump;kill -15 而非 -9;保留 dump 做 MAT 分析;扩堆作为临时措施;根因排查优先修泄漏面试官您好,OOM 的本质就是JVM 认为无法通过 GC 解决内存不足,抛出 java.lang.OutOfMemoryError。
OOM 完整分类(HotSpot 常见 8 种):
典型触发场景:
ByteBuf 未 release()、FileChannel.map() 未 unmap监控手段:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path 让 OOM 时自动 dump 堆,-XX:+ExitOnOutOfMemoryError 让容器快速重启,-Xlog:gc* 打完整 GC 日志,-XX:NativeMemoryTracking=detail 开 NMT 跟踪原生内存jstat -gcutil <pid> 1000 看 GC 频率、jcmd <pid> VM.native_memory summary 看原生内存、jmap -histo:live <pid> 看对象直方图dashboard排查思路:
GC.class_histogram 看 ClassLoader 数量VM.native_memory 或 JFR 事件分析解决矩阵:
-XX:MaxMetaspaceSize,排查 ClassLoader 泄漏release()、JDK 9+ Cleaner、-XX:MaxDirectMemorySize 上限一个实战案例:Netty 服务 Direct buffer memory OOM。现象是堆没满但进程挂掉。排查用 jcmd VM.native_memory 看 DirectByteBuffer 线性增长,定位到某个 handler 没 release() 掉 ByteBuf。修复:Netty 4.1+ 用 ReferenceCountUtil.safeRelease,JFR 加 jdk.DirectByteBuffer 事件监控。
一句话总结:OOM 排查三件套——堆 dump + GC 日志 + 原生内存追踪,配合 METASPACE 泄漏和 DirectBuffer 泄漏的专项排查,就能覆盖 95% 的生产 OOM 场景。