内存溢出需按区域精准定位:堆溢出查对象泄漏与gc,元空间溢出查动态类生成与classloader,直接内存溢出查bytebuffer未释放与系统限制,gc开销过大或栈溢出则分别排查堆压与线程配置。

内存溢出(OutOfMemoryError)不是单一问题,而是 JVM 在不同内存区域无法分配所需内存时抛出的异常。关键要区分是哪块内存“爆了”——堆、元空间、直接内存、线程栈,还是 GC 本身卡住了。定位不准,加内存或调参数往往白忙。
堆内存溢出(java.lang.OutOfMemoryError: Java heap space)
最常见,表现为对象创建过多、生命周期过长,或存在内存泄漏(如静态集合不断 add、缓存未清理、监听器未反注册)。JVM 尝试 GC 后仍无法腾出足够空间,就会报这个错。
- 用 jmap -histo:live
查看存活对象数量和大小,重点关注自定义类和大数组 - 用 jmap -dump:format=b,file=heap.hprof
生成堆转储,用 VisualVM 或 Eclipse MAT 分析谁占了最多内存、是否有可疑的强引用链 - 检查代码中是否误用 static Map/Cache/List 缓存业务对象;确认第三方 SDK(如某些 ORM、RPC 框架)是否有已知内存泄漏 issue
- 合理设置堆参数:避免 -Xms 和 -Xmx 差距过大,建议设为相同值以减少动态扩容开销;新生代比例(-XX:NewRatio)按对象朝生夕死特征调整
元空间溢出(java.lang.OutOfMemoryError: Metaspace)
JDK 8+ 用元空间替代永久代,存放类元数据(类名、字段、方法、常量池等)。频繁动态生成类(如大量使用 CGLIB、Javassist、Spring AOP 代理、Groovy 脚本、OSGi)、或应用热部署次数过多,都可能导致元空间耗尽。
- 监控元空间使用:用 jstat -gc
查看 MU(Metaspace Used)和 MC(Metaspace Capacity) - 限制上限并预留余量:-XX:MaxMetaspaceSize=256m(不设可能吃光系统内存),同时可设 -XX:MetaspaceSize=128m 触发更早 GC
- 排查类加载器泄漏:多个 ClassLoader 加载同一类的不同版本且未被回收,MAT 中可按 “ClassLoader” 分组查看 loaded class 数量
- 减少不必要的字节码增强,评估是否可用 JDK 动态代理替代 CGLIB(后者会生成更多子类)
直接内存溢出(java.lang.OutOfMemoryError: Direct buffer memory)
使用 ByteBuffer.allocateDirect() 分配的堆外内存不受 JVM 堆参数控制,但受 -XX:MaxDirectMemorySize 限制(默认等于 -Xmx)。Netty、NIO 文件通道、部分数据库驱动(如 PostgreSQL 的 pgjdbc)大量使用直接内存,若申请后未显式 clean() 或依赖 System.gc() 回收,容易堆积。
- 启用直接内存跟踪:-XX:+PrintGCDetails -XX:+PrintGCTimeStamps 并观察日志中 DirectMemory 相关统计
- 明确设置上限:-XX:MaxDirectMemorySize=512m,避免默认值引发意外 OOM
- 检查 Netty 等框架是否配置了合理的 maxDirectMemory 或 -Dio.netty.maxDirectMemory;确认 ByteBuffer 使用后调用 cleaner.clean()(或让其自然被 GC)
- 注意 Linux 系统级限制:ulimit -l 控制 mlock 内存上限,过低会影响 direct buffer 分配
GC 开销过大或线程栈溢出
两类易混淆场景:
• java.lang.OutOfMemoryError: GC overhead limit exceeded:GC 花费超 98% 时间却只回收不到 2% 内存,说明堆几乎填满且对象难以释放,本质仍是堆泄漏或容量不足。
• java.lang.StackOverflowError 或 java.lang.OutOfMemoryError: unable to create new native thread:前者是单线程递归/循环调用太深;后者是 JVM 创建线程失败,通常因系统级线程数达上限(ulimit -u),或每个线程栈太大(-Xss 设过高)导致总内存超限。
- 遇到 GC overhead,先按堆溢出流程 dump 分析;临时可加 -XX:-UseGCOverheadLimit 屏蔽该检查(仅用于诊断,勿上线)
- 查系统线程数:ps -T -p
| wc -l ,对比 ulimit -u;优化线程模型,改用线程池而非每请求新建线程 - 调小单线程栈:-Xss256k(默认 1M),尤其在高并发、大量线程场景下能显著提升可创建线程数
- 栈溢出重点查递归逻辑、正则回溯、AOP 过度嵌套等,用 -XX:+PrintGCDetails 和线程 dump(jstack
)交叉分析











