怎么分析 JVM 当前的内存占用情况?OOM 后怎么分析?
怎么分析 JVM 当前的内存占用情况?OOM 后怎么分析?
回答重点
利用 jstat 监控和分析 JVM 内部的垃圾回收、内存等运行状态。可以用它来查看堆内存、非堆内存等的实时状态。
可以使用 jmap 查看, JVM 堆的详细信息(包括堆的配置、内存使用情况、GC 活动等)。
在发生 OOM 时,可以根据 jmap 得到堆转储文件(建议增加JVM启动参数,-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof,在发生 OOM 后自动生成转储文件),再导入到 MAT、VisualVM、GCeasy等工具中分析文件,找出哪些对象占用了大量的内存,再定位到具体的代码解决问题。
JVM 当前的内存占用情况查看
jstat
它是 JDK 自带的工具,用于监控 JVM 各种运行时信息。
1 | jstat -gc <pid> 1000 10 |
- -gc 选项:显示垃圾收集信息(也可以用 gcutil ,gcutil以百分比形式显示内存的使用情况,gc 显示的是内存占用的字节数,以 KB 的形式输出堆内存的使用情况)
- pid:Java 进程的 PID。
- 1000:每 1000 毫秒采样一次。
- 10:采样 10 次。
示例输出:
1 | S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT |
字段含义:
- S0C (Survivor Space 0 Capacity): 第一个 Survivor 区域的容量(字节数)。
- S1C (Survivor Space 1 Capacity): 第二个 Survivor 区域的容量(字节数)。
- S0U (Survivor Space 0 Utilization): 第一个 Survivor 区域的使用量(字节数)。
- S1U (Survivor Space 1 Utilization): 第二个 Survivor 区域的使用量(字节数)。
- EC (Eden Space Capacity): Eden 区域的容量(字节数)。
- EU (Eden Space Utilization): Eden 区域的使用量(字节数)。
- OC (Old Generation Capacity): 老年代的容量(字节数)。
- OU (Old Generation Utilization): 老年代的使用量(字节数)。
- MC (Metaspace Capacity): 方法区(Metaspace)的容量(字节数)。
- MU (Metaspace Utilization): 方法区的使用量(字节数)。
- CCSC (Compressed Class Space Capacity): 压缩类空间的容量(字节数)。
- CCSU (Compressed Class Space Utilization): 压缩类空间的使用量(字节数)。
- YGC (Young Generation GC Count): 年轻代垃圾回收的次数。
- YGCT (Young Generation GC Time): 年轻代垃圾回收的总时间(秒)。
- FGC (Full GC Count): full gc 的次数。
- FGCT (Full GC Time): full gc 的总时间(秒)。
- GCT (Garbage Collection Time): 总的垃圾回收时间(秒)。
注意:如果 FGC 变化频率很高,则说明系统性能和吞吐量将下降,或者可能出现内存溢出。
jmap
用于生成堆转储文件,查看对象分配情况。
1 | jmap -heap <pid> |
示例输出:
1 | Attaching to process ID 1234, please wait... |
字段含义:
- MinHeapFreeRatio: 堆内存最小自由比例。
- MaxHeapFreeRatio: 堆内存最大自由比例。
- MaxHeapSize: 堆内存的最大容量(字节数)。
- NewSize: 新生代的初始容量(字节数)。
- MaxNewSize: 新生代的最大容量(字节数)。
- OldSize: 老年代的初始容量(字节数)。
- NewRatio: 新生代与老年代的比例。
- SurvivorRatio: 新生代中 Survivor 空间的比例。
- MetaspaceSize: 方法区(Metaspace)的初始容量(字节数)。
- CompressedClassSpaceSize: 压缩类空间的容量(字节数)。
- MaxMetaspaceSize: 方法区(Metaspace)的最大容量(字节数)。
- G1HeapRegionSize: G1 垃圾收集器的堆区域大小(字节数)。
Heap Usage 部分的字段解释:
- capacity: 内存区域的总容量(字节数)。
- used: 当前使用的内存量(字节数)。
- free: 当前空闲的内存量(字节数)。
- % used: 使用百分比。
注意:此命令会导致虚拟机暂停工作1~3秒
arthas
输入 dashboard 命令,按回车/enter,会展示当前进程的信息,按ctrl+c可以中断执行。
1 | $ dashboard |
字段含义:
heap: 堆内存的使用情况。
- used 32M: 当前堆内存使用 32MB。
- total 155M: 堆内存总量为 155MB。
- max 1820M: 堆内存最大量为 1820MB。
- usage 1.77%: 堆内存使用百分比为 1.77%。
ps_eden_space: 年轻代 Eden 区域的使用情况。
- used 14M: 当前 Eden 区域使用 14MB。
- total 65M: Eden 区域总量为 65MB。
- max 672M: Eden 区域最大量为 672MB。
- usage 2.21%: Eden 区域使用百分比为 2.21%。
ps_survivor_space: 年轻代 Survivor 区域的使用情况。
- used 4M: 当前 Survivor 区域使用 4MB。
- total 5M: Survivor 区域总量为 5MB。
- max 5M: Survivor 区域最大量为 5MB。
ps_old_gen: 老年代的使用情况。
- used 12M: 当前老年代使用 12MB。
- total 85M: 老年代总量为 85MB。
- max 1365M: 老年代最大量为 1365MB。
- usage 0.91%: 老年代使用百分比为 0.91%。
nonheap: 非堆内存的使用情况。
- used 20M: 当前非堆内存使用 20MB。
- total 23M: 非堆内存总量为 23MB。
code_cache: 代码缓存区的使用情况。
- used 3M: 当前代码缓存区使用 3MB。
- total 5M: 代码缓存区总量为 5MB。
- max 240M: 代码缓存区最大量为 240MB。
- usage 1.32%: 代码缓存区使用百分比为 1.32%。
OOM 分析
第一步使用 jmap 工具生成堆转储文件
1 | jmap -dump:format=b,file=heap_dump.hprof <pid> |
大部分系统内存占用2GB ~ 8GB,此命令会导致虚拟机暂停工作 1~3 秒左右。
可以在 JVM 内存溢出后,主动 dump 生成文件,在启动时增加以下参数即可。
1 | -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof |
第二步使用工具分析堆转储文件
使用 Eclipse MAT (Memory Analyzer Tool) 或 VisualVM 等工具打开 heap_dump.hprof 文件,分析内存泄漏和对象分配情况。
MAT
下图为 MAT 工具,它提供了 Leak Suspects 报告,输出有可能发生内存泄漏的对象:
从上图的左下角可以得知,memoryref.A 这个对象可能产生了内存泄漏!
再简单过一下 MAT 分析内存的其他思路:
可以看直方图,得到占用最多的内存的对象类型是什么:
然后通过右键单击某一行并选择 列出对象(List objects)> 包含传入引用来查看引用它们的内容:
可以追溯找出最终的引用对象是什么:
最终再结合业务代码就能定位到最终的问题,然后修复后经验证即可上线。
GCeasy
GCeasy ,它是一个分析 GC 日志文件的在线网站,能根据上传的 GC 日志,以图表形式分析 GC 情况:
直接在主页上传堆转储文件即可,可以得到 GC 的分析结果,以下就是部分截图: