OOM问题分析过程

因为请求量、环境不同的原因,平时 Java 应用在本地上无法完全复现线上的问题,排查相关问题需要导出堆来分析内存。

产品部署到现场后,随着使用时长、请求量的原因出现了 GC 耗时长、次数频繁的问题.

导出内存快照

排查问题需要导出生产环境的内存堆快照,导出分为手动方式以及自动方式,导出堆快照前需要注意以下事项:

  • 自动导出内存堆快照时,在写Heap Dump文件前通常会触发一次Full GC,所以Heap Dump文件保存的都是Full GC留下的对象信息。

  • 生成dump文件比较耗时,大内存镜像生成dump文件需要更长时间,需要耐心等待。导出的文件体积较大,需要注意硬盘空间是否充足,避免因为硬盘空间不足而引发其他故障。

手动导出

可以使用 JDK 自带的 jmap 命令手动导出。

1
2
jmap -dump:format=b,file=<filename.hprof> <pid>
jmap -dump:live,format=b,file=<filename.hprof> <pid>

说明:

  • <filename.hprof> 中的filename是导出文件的名称,而.hprof是后缀名,方便后续使用MAT等工具选取文件分析,也可以忽略文件后缀。
  • <pid> 是进程ID,可以使用 ps 或者 jps 命令查看应用进程ID。
  • format=b 表示生成的是标准的dump文件,用来进行格式限定
  • -dump:live 指只dump出存活的对象

例子:

1
2
3
4
5
6
7
8
[hadoop_admin@hadoop1 bin]$ jps
21762 HistoryServer
27721 DataNode
30793 Jps
20123 NodeManager
27583 NameNode
12463 RunJar
[hadoop_admin@hadoop1 bin]$ jmap -dump:format=b,file=DataNodeDump.hprof 27721

等待导出完成后,拉取快照文件回本地分析即可,堆文件较大拉回本地可能比较麻烦,可以使用 tar 命令压缩,实测普通压缩效果不明显,可使用gzip压缩。例:

1
[hadoop_admin@hadoop1 bin]$ tar -czvf DataNodeDump.tar.gz DataNodeDump.hprof

自动导出

某次在拉取应用日志排查异常堆栈信息时发现应用曾经 OOM 过(Exception in thread "main" java.lang.OutOfMemoryError)。

当应用发生Full GC或者OOM退出时,内存信息随着应用的退出而烟消云散, 重现OOM的情况比较困难(耗时、或无法重现)。这时候就需要自动导出内存堆快照了,可在启动应用时增加JVM参数。

1
2
3
-XX:+HeapDumpOnOutOfMemoryError #在应用OOM时,导出应用程序内存堆快照
-XX:HeapDumpPath=<filename.hprof> #指定堆快照保存位置,默认在当前目录
-XX:+HeapDumpBeforeFullGC #在应用发生FullGC前,导出应用程序内存堆快照,与OOM互斥

分析内存快照

将内存堆快照拉取回本地后,使用分析工具进行内存使用分析,如 JDK 自带的 jhat,或者Eclipse MemoryAnalyzer Tool(MAT) 、 VisualVM 等等。

其中 JDK 自带的 jhat 命令在 JDK9 之后已经被移除了。MAT 的内存泄漏推断功能算比较强大的工具。以下介绍MAT分析过程

启动MAT可能遇到的错误

Parsing heap dump from ‘xx.hprof‘ Java heap space

错误原因:堆内存文件大于MAT工具的运行时内存

修改 MemoryAnalyzer.ini 文件,将 -Xmx 参数调整为大于堆内存文件的大小即可

JDK版本不对

An error has occurred. See the log file null. 或者 Version 1.8.0 381 of the JVM is not suitable for this product. Version: 17 or greater

修改 MemoryAnalyzer.ini 文件,增加 -vm 参数指定高版本JDK(JDK11、17)路径