Skip to content

故障诊断-高内存占用、内存泄露 ​

看在前面 ​

一、诊断思路 ​

在 OOM 触发时,我们的程序可能已经挂掉或者假死了。一般情况我们可能会重启程序,一边运行一边定位故障。一直循环这个过程,直到定位到根源问题。

1)程序挂掉时,自动生成 dump 文件进行分析。 ​

使用 JVM 参数获取 dump 文件

  • -XX:+HeapDumpOnOutOfMemoryError,当 OutOfMemoryError 发生时自动生成 Heap Dump 文件。

  • -XX:+HeapDumpBeforeFullGC,当 JVM 执行 FullGC 前执行 dump。

  • -XX:+HeapDumpAfterFullGC,当 JVM 执行 FullGC 后执行 dump。

  • -XX:+HeapDumpOnCtrlBreak,交互式获取 dump。在控制台按下快捷键 Ctrl + Break 时,JVM 就会转存堆快照。

  • -XX:HeapDumpPath=/test.hprof,指定 dump 文件存储路径。

2)程序运行时,同步进行故障诊断,大体思路为: ​

  • 哪些对象导致堆空间增长(堆空间直方图)

  • 哪些对象不能被垃圾回收

  • GC 回收的表现,年轻代与老年代的回收频率结果,Full GC 的触发时机

  • 使用本机内存跟踪(NMT)实时比对各内存区域增减情况

诊断过程发现一定规律后,尝试生成 dump 文件分析

3)分析 dump 文件思路 ​

  • 哪些对象较大,数量较多

  • 大对象的占用率

  • 大对象的引用栈,分析无效引用

本文内容主要讲如何观察堆空间直方图及 dump 文件分析。

JDK 相关命令详细用法可参考 Java JVM JDK13 诊断命令处理工具 jps,jstat,jinfo,jmap,jstack,jcmd

二、诊断过程 ​

1. 定位 PID ​

执行 jps -l 命令,定位高内存占用的 PID

java
jps -l
1174 org.elasticsearch.bootstrap.Elasticsearch

2. 使用本机内存跟踪(NMT)追踪内存变化情况 ​

VM 参数配置启用内存跟踪(会导致 5%-10%的性能开销),-XX:NativeMemoryTracking=[off|summary|detail]。

如果在 JVM 退出时打印追踪信息配置: -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics

jcmd NMT Option描述
off关闭,默认处于该状态
summary收集摘要信息
detail收集详细信息

执行 jcmd VM.native_memory 追踪统计,命令支持功能如下:

java
jcmd <pid> VM.native_memory [summary|detail|baseline|summary.diff|detail.diff|shutdown] [scale= KB|MB|GB]

jcmd NMT Option | 描述 |。 -|-| summary | 收集摘要信息|。 detail | 收集详细信息|。 summary.diff | 根据最后一个基准打印新的摘要报告|。 detail.diff | 根据最后一个基准打印新的详细报告|。 shutdown | 停止本机内存跟踪|。

使用示例:

java
jcmd 5460 VM.native_memory baseline   ———— 创建一个基准快照

jcmd 5460 VM.native_memory detail.diff  —————— 一段时间后进行比较

———————— 部分打印信息如下:
Total: reserved=664624KB  -20610KB, committed=254344KB -20610KB  <--- 与基线比较,+增加,-减少
 
-  Java Heap (reserved=516096KB, committed=204800KB)
             (mmap: reserved=516096KB, committed=204800KB)
 
-  Class (reserved=6578KB +3KB, committed=4530KB +3KB)
         (classes #668 +3)                           <--- 3 个类被加载
         (malloc=434KB +3KB, #930 -7)                <--- malloc 的内存增加了 3KB,但是 malloc 的数量减少了 7
         (mmap: reserved=6144KB, committed=4096KB)
   ...
   ...

更多内容可参考官方文档Native Memory Tracking, 使用 NMT 检测内存泄漏

3. 分析堆直方图、生成 dump 文件 ​

建议使用最新的 jcmd 而不是 jmap 实用程序,以增强诊断功能并降低性能开销。jcmd 命令提供了很多诊断功能,参考上面的处理工具文章链接使用。

下面小节中分别介绍 jcmd、jmap 2 个命令的使用,选择你合适的即可。

dump 文件太大时请注意,为了保证 dump 的信息是可靠的,所以会暂停应用程序。如果 jmap 添加了 :live 参数后,JVM 会先触发 gc,然后再统计信息。

3.1 使用 jcmd 命令分析 ​

  1. 执行 jcmd PID GC.class_histogram 命令查看堆直方图,示例如下:
java
jcmd 11704 GC.class_histogram
11704:
 num     #instances         #bytes  class name (module)
-------------------------------------------------------
   1:       2007799       48573456  [B (java.base@11.0.5)
   2:       2007232       48173568  java.lang.String (java.base@11.0.5)
   3:       2000000       48000000  io.gourd.java.jvm.oom.Oom$OomKey
   4:       1003838       32122816  java.util.HashMap$Node (java.base@11.0.5)
   5:           322        8435136  [Ljava.util.HashMap$Node; (java.base@11.0.5)

输出显示堆中每种类类型的总大小和实例计数。如果获得了一系列直方图(例如,每 2 分钟一次),则您可能能够观察到可进一步分析的趋势。

  1. 执行 jcmd PID GC.heap_dump filename=filename 生成 dump 文件

3.2 使用 jmap 命令分析 ​

  1. 执行 jmap -histo:live PID 命令查看堆直方图,示例如下:
java
jmap -histo:live 1174
 num     #instances         #bytes  class name (module)
-------------------------------------------------------
   1:          7570         503328  [B (java.base@11.0.2)
   2:          6987         167688  java.lang.String (java.base@11.0.2)
   3:          1224         150344  java.lang.Class (java.base@11.0.2)
   4:          3825         122400  java.util.HashMap$Node (java.base@11.0.2)
   
... 更多内容未粘贴
  1. 执行 jmap -dump:live,format=b,file=test.dump PID 将存活对象转换为 dump 文件离线分析

4. dump 文件分析 ​

jvisualvm-大对象分析

JDK 自带的 jvisualvm 工具分析

jprofile-大对象分析

实际生产过程中我们可以选择更多的工具运行监控、分析 dump 文件: