java应用OOM快速定位与解决方法

背景

最近项目老是出现OOM问题,常见有以下错误:

java.lang.OutOfMemoryError: PermGen space
java.lang.OutOfMemoryError: Java heap space
    

OOM的常见原因

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

Java代码导致OutOfMemoryError错误的解决

  • 检查代码中是否有死循环或递归调用。
  • 检查是否有大循环重复产生新对象实体。
  • 检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
  • 检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

定位代码解决

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

top

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

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

注意

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

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

输入以下命令:

jmap -heap 10410

Attaching to process ID 10410, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.91-b14

using thread-local object allocation.
Parallel GC with 4 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 2147483648 (2048.0MB)
   NewSize                  = 44564480 (42.5MB)
   MaxNewSize               = 715653120 (682.5MB)
   OldSize                  = 89653248 (85.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 = 343408640 (327.5MB)
   used     = 63192336 (60.26490783691406MB)
   free     = 280216304 (267.23509216308594MB)
   18.401498576156964% used
From Space:
   capacity = 18350080 (17.5MB)
   used     = 12886976 (12.28997802734375MB)
   free     = 5463104 (5.21002197265625MB)
   70.22844587053571% used
To Space:
   capacity = 18874368 (18.0MB)
   used     = 0 (0.0MB)
   free     = 18874368 (18.0MB)
   0.0% used
PS Old Generation
   capacity = 80216064 (76.5MB)
   used     = 24040136 (22.92646026611328MB)
   free     = 56175928 (53.57353973388672MB)
   29.969229106030433% used

23018 interned Strings occupying 2885744 bytes.

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

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

jmap -histo:live 10410 | more

      num     #instances         #bytes  class name
----------------------------------------------
   1:         59259        8998824  [C
   2:         21537        1895256  java.lang.reflect.Method
   3:         57709        1385016  java.lang.String
   4:          2683        1063512  [B
   5:          9175        1021368  java.lang.Class
   6:         18681         747240  java.util.LinkedHashMap$Entry
   7:         21370         683840  java.util.concurrent.ConcurrentHashMap$Node
   8:          8166         574544  [Ljava.util.HashMap$Node;
   9:          9977         558712  java.util.LinkedHashMap
  10:          9548         518480  [Ljava.lang.Object;
  11:         21735         472376  [Ljava.lang.Class;
  12:         13345         427040  java.util.HashMap$Node
  13:          5570         401040  java.lang.reflect.Field
  14:           259         258400  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  15:         14774         236384  java.lang.Object
  16:          2692         215360  java.lang.reflect.Constructor
  17:          3413         198216  [Ljava.lang.reflect.Method;
  18:          4133         160288  [Ljava.lang.String;
  19:          3695         147800  java.lang.ref.SoftReference
  20:          1537         147552  org.springframework.beans.GenericTypeAwarePropertyDescriptor
  21:          2378         133168  java.lang.Class$ReflectionData
  22:          2861         132256  [I
  23:          4050         129600  java.util.LinkedList
  24:          3749         119968  java.lang.ref.WeakReference
  25:          2435         116880  java.util.HashMap
  26:          4509         108216  java.util.ArrayList
  27:          4080          97920  java.beans.MethodRef
  28:          2154          86160  java.util.TreeMap$Entry
  29:          1188          85536  org.springframework.core.annotation.AnnotationAttributes
  30:          1126          72064  org.springframework.core.MethodParameter
  31:          2858          68592  java.util.LinkedList$Node
  32:          3928          62848  java.util.LinkedHashSet
  33:           370          62160  org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$ConfigurationClassBeanDefinition
  34:          2185          52440  sun.reflect.generics.tree.SimpleClassTypeSignature
  35:          2016          48384  sun.reflect.annotation.AnnotationInvocationHandler
  36:           961          46128  org.springframework.core.ResolvableType
  37:           901          43248  org.apache.tomcat.util.modeler.AttributeInfo
  38:          2023          43200  [Ljava.lang.reflect.Type;
  39:          2570          41120  java.util.LinkedHashMap$LinkedKeySet
  40:          2185          41096  [Lsun.reflect.generics.tree.TypeArgument;
  41:          1282          41024  java.util.concurrent.locks.ReentrantLock$NonfairSync
  42:          2472          39552  java.util.LinkedHashMap$LinkedEntrySet
  43:          2374          37984  org.springframework.core.annotation.AnnotationUtils$DefaultValueHolder
  44:          1593          37008  [Ljava.lang.reflect.Constructor;

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

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

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

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

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

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

输入以下命令,查看进程的线程数

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

输入以下命令,查看进程的句柄数

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

jmap 附加说明

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