怎么分析 JVM 当前的内存占用情况?OOM 后怎么分析?

Sherwin.Wei Lv7

怎么分析 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
2
3
4
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC       MU       CCSC    CCSU     YGC     YGCT     FGC    FGCT     GCT
1536.0 1536.0 0.0 0.0 30720.0 1024.0 708608.0 2048.0 44800.0 43712.6 4864.0 4096.0 4 0.072 1 0.015 0.087
1536.0 1536.0 0.0 0.0 30720.0 2048.0 708608.0 2048.0 44800.0 43712.6 4864.0 4096.0 4 0.072 1 0.015 0.087
1536.0 1536.0 0.0 0.0 30720.0 3072.0 708608.0 2048.0 44800.0 43712.6 4864.0 4096.0 4 0.072 1 0.015 0.087

字段含义:

  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
Attaching to process ID 1234, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11

using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC

Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 1048576000 (1000.0MB)
NewSize = 1310720 (1.25MB)
MaxNewSize = 17592186044415 MB
OldSize = 8388608 (8.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 46989312 (44.8125MB)
used = 14364528 (13.697036743164062MB)
free = 32624784 (31.115463256835938MB)
30.57471507400737% used
Eden Space:
capacity = 41943040 (40.0MB)
used = 12058624 (11.5MB)
free = 29884416 (28.5MB)
28.769444942474365% used
From Space:
capacity = 5036288 (4.8046875MB)
used = 2305904 (2.1997528076171875MB)
free = 2730384 (2.6049346923828125MB)
45.8012652387619% used
To Space:
capacity = 5036288 (4.8046875MB)
used = 0 (0.0MB)
free = 5036288 (4.8046875MB)
0.0% used
concurrent mark-sweep generation:
capacity = 100663296 (96.0MB)
used = 1433600 (1.3671875MB)
free = 99229696 (94.6328125MB)
1.4241612307230632% used

10764 interned Strings occupying 826944 bytes.

字段含义:

  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ dashboard
ID NAME GROUP PRIORI STATE %CPU TIME INTERRU DAEMON
17 pool-2-thread-1 system 5 WAITIN 67 0:0 false false
27 Timer-for-arthas-dashb system 10 RUNNAB 32 0:0 false true
11 AsyncAppender-Worker-a system 9 WAITIN 0 0:0 false true
9 Attach Listener system 9 RUNNAB 0 0:0 false true
3 Finalizer system 8 WAITIN 0 0:0 false true
2 Reference Handler system 10 WAITIN 0 0:0 false true
4 Signal Dispatcher system 9 RUNNAB 0 0:0 false true
26 as-command-execute-dae system 10 TIMED_ 0 0:0 false true
13 job-timeout system 9 TIMED_ 0 0:0 false true
1 main main 5 TIMED_ 0 0:0 false false
14 nioEventLoopGroup-2-1 system 10 RUNNAB 0 0:0 false false
18 nioEventLoopGroup-2-2 system 10 RUNNAB 0 0:0 false false
23 nioEventLoopGroup-2-3 system 10 RUNNAB 0 0:0 false false
15 nioEventLoopGroup-3-1 system 10 RUNNAB 0 0:0 false false
Memory used total max usage GC
heap 32M 155M 1820M 1.77% gc.ps_scavenge.count 4
ps_eden_space 14M 65M 672M 2.21% gc.ps_scavenge.time(m 166
ps_survivor_space 4M 5M 5M s)
ps_old_gen 12M 85M 1365M 0.91% gc.ps_marksweep.count 0
nonheap 20M 23M -1 gc.ps_marksweep.time( 0
code_cache 3M 5M 240M 1.32% ms)
Runtime
os.name Mac OS X
os.version 10.13.4
java.version 1.8.0_162
java.home /Library/Java/JavaVir
tualMachines/jdk1.8.0
_162.jdk/Contents/Hom
e/jre

字段含义:

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 报告,输出有可能发生内存泄漏的对象:

image.png

从上图的左下角可以得知,memoryref.A 这个对象可能产生了内存泄漏!

再简单过一下 MAT 分析内存的其他思路:

可以看直方图,得到占用最多的内存的对象类型是什么:

image.png

然后通过右键单击某一行并选择 列出对象(List objects)> 包含传入引用来查看引用它们的内容:

image.png

可以追溯找出最终的引用对象是什么:

image.png

最终再结合业务代码就能定位到最终的问题,然后修复后经验证即可上线。

GCeasy

GCeasy ,它是一个分析 GC 日志文件的在线网站,能根据上传的 GC 日志,以图表形式分析 GC 情况:

image.png

直接在主页上传堆转储文件即可,可以得到 GC 的分析结果,以下就是部分截图:

image.png image.png
Comments