背景
最近比较多的碰到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
|
|
可以看到详细的堆内存使用情况,可以以此判断应用分配的内存是否确实过小。
判断是否是由于“频繁创建对象,没有及时回收”
输入以下命令,找出最耗内存的对象:
jmap -histo:live 2879 | more
|
|
输入命令后,会以表格的形式显示存活对象的信息,并按照所占内存大小排序。
- instances: 对象实例数量
- bytes: 占用内存大小
- class name: 类名
可以看到目前最耗内存的对象也才占用内存11m,所以属于正常范畴
如果发现某个对象的占用大量内存(例如:1G以上),就需要review代码,审查下该对象是否没有及时回收
PS:其中输出的奇怪的class name请查看最后的附录。
判断是否是由于“频繁申请系统资源”
- 查看进程的线程数量:
ll /proc/{PID}/task | wc -l
|
|
可以看到,该进程使用了101个线程。
如果看到启用了大量线程,就需要审查代码涉及到线程池的使用部分,是否限定了最大线程数量。
- 查询进程占用的句柄数量
ll /proc/{PID}/fd | wc -l
|
|
可以看到,该进程使用了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 |