Zacard's Notes

线上java项目OOM快速定位与解决方法

背景

最近比较多的碰到OOM异常,总结下OOM的异常快速定位和解决的办法。

OOM的常见原因

  • 内存分配确实过小
  • 频繁创建对象,没有及时释放
  • 频繁申请系统资源,导致系统资源耗尽(例如:不断创建线程,不断发起网络连接)

定位与解决方法

需要先找到出问题的进程,使用top命令定位:

top

输入top命令后,可以按P(shift+p)根据cpu占用排序、按M根据内存占用排序、按T根据运行时间排序。(可以先按c显示具体的command)

这里先按M根据内存排序查找异常的进程:这里假设出现异常的进程pid为2879

注意

定位问题前请先尝试输入jps命令,确定是否能够显示出现问题的pid(例如2879).如果jps没有相应的显示,可能是你当前用户的权限不够,请使用启用相应进程的用户或者拥有更高权限的用户排查问题!不然以下的一些命令(例如jmap)将无法使用.

原理:java程序启动后,默认会在/tmp/hsperfdata_userName目录下以该进程的id为文件名新建文件,并在该文件中存储jvm运行的相关信息,其中的userName为当前的用户名,/tmp/hsperfdata_userName目录会存放该用户所有已经启动的java进程信息.而jps、jconsole等工具的数据来源就是这个文件(/tmp/hsperfdata_userName/pid)。所以当该文件不存在或是无法读取时就会出现jps无法查看该进程号,jconsole无法监控等问题

判断是否是由于“内存分配确实过小”

输入以下命令:

jmap -heap 2879

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
[root@node182 ~]# jmap -heap 2879
Attaching to process ID 2879, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.73-b02
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2040528896 (1946.0MB)
NewSize = 42467328 (40.5MB)
MaxNewSize = 680001536 (648.5MB)
OldSize = 85458944 (81.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 28311552 (27.0MB)
used = 21982144 (20.96380615234375MB)
free = 6329408 (6.03619384765625MB)
77.64372649016204% used
From Space:
capacity = 1048576 (1.0MB)
used = 688128 (0.65625MB)
free = 360448 (0.34375MB)
65.625% used
To Space:
capacity = 1048576 (1.0MB)
used = 0 (0.0MB)
free = 1048576 (1.0MB)
0.0% used
PS Old Generation
capacity = 100139008 (95.5MB)
used = 90236608 (86.05633544921875MB)
free = 9902400 (9.44366455078125MB)
90.11134602012434% used
28237 interned Strings occupying 3386232 bytes.

可以看到详细的堆内存使用情况,可以以此判断应用分配的内存是否确实过小。

判断是否是由于“频繁创建对象,没有及时回收”

输入以下命令,找出最耗内存的对象:

jmap -histo:live 2879 | more

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@node182 ~]# jmap -histo:live 2879 | more
num #instances #bytes class name
----------------------------------------------
1: 81515 11259264 [C
2: 80010 1920240 java.lang.String
3: 44265 1416480 java.util.concurrent.ConcurrentHashMap$Node
4: 4057 1369872 [B
5: 11200 1241976 java.lang.Class
6: 8977 789976 java.lang.reflect.Method
7: 18801 752040 java.util.LinkedHashMap$Entry
8: 5144 627336 [I
9: 11948 601184 [Ljava.lang.Object;
10: 8275 595352 [Ljava.util.HashMap$Node;
11: 18231 583392 java.lang.ref.WeakReference
12: 9847 551432 java.util.LinkedHashMap
13: 375 544288 [Ljava.util.concurrent.ConcurrentHashMap$Node;
14: 29118 465888 java.lang.Object
15: 13034 417088 java.util.HashMap$Node
16: 8850 354000 java.lang.ref.SoftReference
17: 10490 251760 java.beans.MethodRef
18: 3586 200816 java.beans.MethodDescriptor
19: 6450 195288 [Ljava.lang.String;

输入命令后,会以表格的形式显示存活对象的信息,并按照所占内存大小排序

  • instances: 对象实例数量
  • bytes: 占用内存大小
  • class name: 类名

可以看到目前最耗内存的对象也才占用内存11m,所以属于正常范畴

如果发现某个对象的占用大量内存(例如:1G以上),就需要review代码,审查下该对象是否没有及时回收

PS:其中输出的奇怪的class name请查看最后的附录。

判断是否是由于“频繁申请系统资源”

  • 查看进程的线程数量:

ll /proc/{PID}/task | wc -l

1
2
3
[root@node182 ~]# ll /proc/2879/task | wc -l
101

可以看到,该进程使用了101个线程。

如果看到启用了大量线程,就需要审查代码涉及到线程池的使用部分,是否限定了最大线程数量。

  • 查询进程占用的句柄数量

ll /proc/{PID}/fd | wc -l

1
2
3
[root@node182 ~]# ll /proc/2879/fd | wc -l
38

可以看到,该进程使用了38个文件句柄。

如果占用大量文件句柄,需要审查代码中涉及到文件操作和网络连接操作是否有及时关闭资源链接。

jmap 附加说明

jmap -heap输出的非自定义类名说明:

BaseType Character Type Interpretation
B byte signed byte
C char Unicode character
D double double-precision floating-point value
F float single-precision floating-point value
I int integer
J long long integer
L; reference an instance of class
S short signed short
Z boolean true or false
[ reference one array dimension
坚持原创技术分享,您的支持将鼓励我继续创作!

热评文章