服务器与大带宽专家 · 持牌IDC/CDN/ISP服务商
简米科技官网JIANMI TECH
资讯 2026-10-11 更新于 2026-10-11 简米科技 4,317 字 10 分钟阅读

JVM内存溢出如何排查?Java虚拟机内存泄漏怎么办

导读JVM内存溢出排查的核心思路是先通过异常日志定位异常类型与触发位置,再结合代码上下文与堆现场分析占用主体,最后针对根因调整配置或修复代码,整个排查过程按“日志 -> 线程栈 -> 堆转储 -> 代码定位 -> 参数调优”的顺序展开即可,JVM内存溢出如何排查?先分清是哪块内存出了问题Ja……

JVM内存溢出排查的核心思路是先通过异常日志定位异常类型与触发位置,再结合代码上下文与堆现场分析占用主体,最后针对根因调整配置或修复代码,整个排查过程按“日志 -> 线程栈 -> 堆转储 -> 代码定位 -> 参数调优”的顺序展开即可。

JVM内存溢出如何排查?先分清是哪块内存出了问题

Java虚拟机把运行时内存划分成几个区域,内存溢出的症状和根因各不相同,排查前,先看异常信息中的关键字,这一眼能确定大方向。

常见OOM异常信息与对应区域

  • java.lang.StackOverflowError:线程栈溢出,多出现在递归调用过深、死循环调用,这类问题往往不伴随堆内存压力,错误信息会直接指向抛出异常的代码行。
  • java.lang.OutOfMemoryError: Java heap space:堆内存溢出,对象实例整体占用超过堆上限,大多数线上OOM属于此类,需要重点分析。
  • java.lang.OutOfMemoryError: Metaspace:元空间溢出,常见于动态生成类的场景,如CGLIB代理、大量JSP编译。
  • java.lang.OutOfMemoryError: unable to create new native thread:操作系统层面无法创建新线程,多与线程数超限或系统进程限制有关。
  • java.lang.OutOfMemoryError: GC overhead limit exceeded:垃圾回收占用了98%以上CPU时间,但回收到的堆空间不到2%,属于堆内存压力的极端表现。

根据错误日志缩小排查范围

实际排查时,第一步永远是把完整异常栈和历史日志抓下来。切勿只看第一行错误就重启服务,那会把现场丢掉,完整异常栈里通常包含at com.xxx.ClassName.methodName(ClassName.java:123)这类调用链,直接指到业务代码的位置。

日志里还有一条关键信息:java.lang.OutOfMemoryError抛出时,如果JVM启动参数里配置了-XX:+HeapDumpOnOutOfMemoryError,会生成一个.hprof格式的堆转储文件,文件路径和文件名也会打印在异常日志中,这个转储文件就是排查堆内存溢出的关键证据。

Java虚拟机内存溢出解决办法:分场景处理才是关键

不同内存区域的溢出有不同的解法,这一节按最常见的两类场景拆开说。

堆内存溢出的排查路径

堆内存溢出是线上最常见的问题,处理节奏通常分三步走。

第一步,拿堆转储文件。 如果启动时没加-XX:+HeapDumpOnOutOfMemoryError

JVM内存溢出如何排查?Java虚拟机内存泄漏怎么办

参数,而服务还在运行,可以用jmap命令手动导出,命令格式为jmap -dump:live,format=b,file=当前时间.hprof 进程PID。-dump:live参数只导出存活对象,文件会更小,但会触发一次Full GC,有停顿风险,生产环境需要谨慎操作。

第二步,分析堆转储文件。 堆转储分析工具选择很多,免费工具推荐Eclipse MAT(Memory Analyzer),打开.hprof文件后,MAT的“Overview”页面会展示“Leak Suspects”(泄漏嫌疑点)报告,直接点击“Details”可以看到疑似泄漏的对象链关系,分析重点看两个维度:

  • 对象数排名:哪个类的实例数量异常多。
  • 保留堆大小排名:哪个对象自身的“保留集合”占用了最大空间。

保留集合的概念比较关键,它指的是“如果把这个对象回收掉,整体能释放多少内存”的评估结果,比对象自身大小更能说明问题。

第三步,对照代码定位根因。 拿到对象类型后,回到业务代码里搜索该对象的创建逻辑,常见根因有:

  • 集合类的静态变量持有大量数据,比如private static Map<String, Object> CACHE = new HashMap<>();。
  • 批量操作未分批处理,一次加载了太多数据到内存。
  • 数据库查询结果集过大,LIMIT关键字缺失或未生效。
  • 线程池的阻塞队列容量配置过大,积压了大量任务对象。

Metaspace溢出的处理思路

Metaspace溢出在动态代理、字节码生成较多的框架场景下比较集中,排查这类问题,重点不是堆转储(因为堆可能很健康),而是jstat命令输出的Metaspace使用量趋势。

jstat -gcutil 进程PID 500,每500毫秒打印一次GC和内存使用情况,注意观察M(Metaspace)列,如果数值持续上升且无视GC回落,基本锁定是类加载器未释放或类生成过多。

处理方式有两条路:

  • 调整参数:-XX:MaxMetaspaceSize适当放大,通常从默认的几百MB调整到1GB至2GB能暂时缓解。
  • 根治问题:检查动态代理类是否被反复创建和缓存,检查自定义类加载器是否正确关闭。

实战Java虚拟机吧里高频讨论的排查场景

Java虚拟机吧里有个高频问题:“昨天半夜OOM导致服务挂了几次,重启后恢复正常,没过两天又挂了,怎么办?”这个问题背后是一条典型链条:

JVM内存溢出如何排查?Java虚拟机内存泄漏怎么办

内存泄漏 => 堆占用逐渐上升 => 兜底触发OOM => 重启清零 => 周而复始。

线上OOM排查实操完整步骤

假设你的服务进程PID是12345,按下面顺序操作能走通排查流程。

第一个动作:收集GC日志和堆转储

  • 通过jgcutil或JFR(Java Flight Recorder)获取GC日志,确认老年代占用是否持续走高。
  • 检查应用启动脚本,确认是否已配置-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/,没有的话,等下次OOM之前,先用jmap -dump:format=b,file=oops.hprof 12345手动导一次。

第二个动作:用MAT定位泄漏嫌疑点
MAT打开oops.hprof后,进入“Leak Suspects”界面,能看到类似“One instance of com.example.service.OrderService is responsible for 2.1GB ”的描述,点击“See stacktrace”能看到对象的分配调用链,这个调用链是你回到代码里定位问题的最短路径。

第三个动作:验证并修复
确认根因是全局静态Map缓存未清理后,修复方案二选一:

  • 减少缓存写入量,或者给Map设定最大容量,超出后按LRU策略淘汰。
  • 引入成熟缓存框架,如Caffeine,让过期策略由框架统一管理。

排查工具怎么选

  • JProfiler:商用,功能最全,支持本地和远程分析,可视化程度高,价格较高。
  • VisualVM:免费,JDK自带,支持插件扩展,能远程连接,适合日常快速查看堆和线程状态。
  • Eclipse MAT:免费,专注堆转储分析,泄漏嫌疑点识别能力强,推荐作为主力工具。
  • Arthas:阿里开源的诊断工具,能在线查看类加载、方法调用、JVM实时状态,适合排查“线上不能随便重启”的灰度环境,不需要预先配置参数。

顺手补充一个点:排查时先把应用的启动参数完整看一遍,特别关注-Xmx、-Xms、-XX:MaxMetaspaceSize这几个关键值,多数情况下,OOM的触发不是堆太小,而是某处代码泄漏,一味调大堆只延后了爆发点。

JVM内存溢出排查时的几个常见误区

看到OOM就调大堆内存

调大堆是应急手段,不是解决方案,如果存在内存泄漏,调大堆后问题会以更长的周期复发,而且更大的堆意味着更长的GC停顿时间。

JVM内存溢出如何排查?Java虚拟机内存泄漏怎么办

只分析堆转储,忽略JVM线程状态

有些OOM场景伴随线程阻塞,比如大对象分配时Full GC造成了线程停顿,导致外部表现为请求超时,遇到这类场景,需要同步抓取线程栈:jstack 进程PID > thread_dump.txt,重点查看线程状态是WAITING(等待)、BLOCKED(阻塞)还是一直在RUNNABLE。

不关注“GC overhead limit exceeded”之前的GC日志

这个错误意味着GC已经快累垮了JVM,排查时需要往前翻GC日志,看Full GC的频率和耗时变化,如果Full GC越来越频繁、每次回收量却很少,说明堆里大多数是存活对象,这本身也是泄漏信号。

Q&A:Java虚拟机内存溢出排查常见问题汇总

Q1:Java虚拟机内存溢出排查有没有直接可用的命令?

有,按顺序用这三个命令就能覆盖大多数场景:jps -l查看当前运行的Java进程PID,jmap -dump:live,format=b,file=dump.hprof 进程PID导出堆快照,jstack 进程PID查看线程快照,如果本地没有jmap或者权限受限,可以改用jcmd 进程PID GC.heap_dump dump.hprof,效果相同。

Q2:本地无法复现OOM,堆转储文件怎么搞?

用参数-XX:+HeapDumpOnOutOfMemoryError让JVM在OOM发生时自动导出文件,同时配合-XX:HeapDumpPath指定导出路径,文件导出来后,Eclipse MAT或VisualVM都能直接分析,如果文件特别大(超过2GB),建议在MAT的Memory Analyzer配置里调大-Xmx参数,否则可能因为自身内存不足而打开失败。

Q3:OOM进程被系统直接杀掉了,什么日志都没留下怎么办?

这种情况通常发生在内存交换过度或cgroup限制的容器环境中,操作系统触发OOM Killer把进程强制终止,排查时首先查看系统日志和应用自身的GC日志,确认进程崩溃前的JVM内存曲线是否已接近容器上限,重点检查容器内存占用情况,很多情况下堆外内存(直接内存)占用了大量空间,而堆内还没有满,可以尝试把-XX:MaxDirectMemorySize显式设小,并观察是否缓解。


内存溢出排查本身是个经验活,但路径是固定的:拿到异常类型,导出堆转储,分析对象占用,回到代码定位,再决定调参还是修复,按这个顺序走完,绝大多数OOM问题都能找到明确的根源,即便遇到特别隐蔽的泄漏,也能确定内存瓶颈所在,为后续优化提供明确方向。

分享本文
本文为 简米科技官网 原创,已由运维技术专家审核。转载请注明来源:原文链接
售前咨询 服务热线 售后 邮箱