本文介绍几种常见的jvm溢出,并配合常见的工具进行溢出的分析
- 堆内存溢出
1、虚拟机规范里关于堆内存溢出,给出的解释是:如果实际需要的堆超过了内存管理系统所提供的最大容量,那么JAVA虚拟机会抛出一个OutOfMemory的异常,我们知道java堆也就是heap区,存放的是实例化的对象和数组,当需要申请空间存放对象或数组时,当然了肯定是经过局部或者全局GC以后空间仍然不够,则会出现内存溢出的异常
2、
2.1异常示例代码构造:public class HeapOutOfMemory { public void createHeapOutOfMemory() { boolean isStop = false; int i = 0; List<byte[]> list = new ArrayList<byte[]>(); while(!isStop) { try { byte[] array = new byte[1024*1024];//1mb i++; list.add(array); }catch(Throwable e) { e.printStackTrace(); isStop = true; System.out.println("createHeapOutOfMemory i is: " + i); } } } }
2.2设置java启动参数
JAVA_OPTS=" -Xmx50m -Xms50m -Xmn20m -Xss256k -XX:MetaspaceSize=500m -XX:MaxMetaspaceSize=1000m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/work/doc/logs"
-Xmx设置最大堆内存
-Xms设置初始堆内存
-Xmn设置新生代内存
-Xss设置每一个线程栈的大小
-XX:MetaspaceSize元数据空间的初始大小
-XX:MaxMetaspaceSize元数据空间的最大大小
-XX:+HeapDumpOnOutOfMemoryError发生heap溢出时进行生成堆内存快照
-XX:HeapDumpPath指定快照存储的路径
补充说明下,另外一种触发堆内存快照并存储的方法:
jmap -dump:format=b,file=/work/doc/logs pid
3、异常提示:
4、溢出分析
4.1、启动命令:jhat -J-Xmx1024M /work/doc/logs/java_pid16947.hprof
查看堆转储信息:http://localhost:7000/histo/Class Instance Count Total Size class [B 786 35968646 class [C 29234 6045722 class java.lang.reflect.Method 3524 458120 class [Ljava.lang.Object; 3914 425928
耗费内存最严重的是class[B,786个对象,耗费了35M,这个需要进去关注下,点击进去后
发现有存在多个1m多的大对象,下面截取部分示例
[B@0x7be467010 (1048592 bytes) : ??
[B@0x7bcfbeb80 (524 bytes) : ??
[B@0x7bd16b3d0 (21 bytes) : ??
[B@0x7bcfc0f50 (70 bytes) : ??
[B@0x7bd1629d0 (48 bytes) : ??
[B@0x7bd171248 (19 bytes) : ??
[B@0x7bcfc02e8 (344 bytes) : ??
[B@0x7bd1791d0 (25 bytes) : ??
[B@0x7be6be8a8 (1048592 bytes) : ??
那如何找到该大对象对应的业务代码呢,找到业务代码,可以进行具体分析,这样离问题解决才会更进一步
4.2、点击该大对象,找到References to this object,发现是一个数组对象引用了该大对象,如下:
Array of 33 objectsClass:
class [Ljava.lang.Object;Values
0 : [B@0x7bd75d150 (1048592 bytes)
1 : [B@0x7bda66f70 (1048592 bytes)
2 : [B@0x7bdb66f80 (1048592 bytes)
3 : [B@0x7bde66fb0 (1048592 bytes)
4 : [B@0x7bdf66fc0 (1048592 bytes)
5 : [B@0x7be266ff0 (1048592 bytes)
6 : [B@0x7be367000 (1048592 bytes)
接着查看引用References to this object:会看到:
java.util.ArrayList@0x7bd6e4810 (32 bytes) : field elementData
点击进入,查看发现是一个list,大家知道list是基于数组封装的,如下:instance of java.util.ArrayList@0x7bd6e4810 (32 bytes)
Class:
class java.util.ArrayList
查看该数组实例的若引用,Include weak refs,点击Java Local Reference找到该大对象产生时候的源码位置,可以分析逻辑了:com.xxxx.sample.oom.HeapOutOfMemory.createHeapOutOfMemory(()V) : HeapOutOfMemory.java line 23
4.3、总结:首先找到可疑的大对象,一步一步,找到该对象的引用,根据引用定位到对应的源码,进行下一步分析 - 虚拟机线程栈溢出
1、虚拟机规范里是这么解释的:
1.1、如果线程请求分配的栈容量超过 Java 虚拟机栈允许的最大容量时,Java 虚拟机将会抛出一个 StackOverflowError 异常
1.2、如果 Java 虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是目前无法申请到足够的内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的虚拟机栈,那 Java 虚拟机将会抛出一个 OutOfMemoryError 异常
2、异常代码构造public class StackOutOfMemory { public int i = 0; public void call() { i++; call(); } }
2.1、设置JVM参数:
JAVA_OPTS=" -Xmx50m -Xms50m -Xmn20m -Xss160k -XX:MetaspaceSize=500m -XX:MaxMetaspaceSize=1000m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/work/doc/logs"
2.2、异常查看 - 方法区溢出
1、虚拟机规范中的解释是:如果方法区的内存空间不能满足内存分配请求,那Java虚拟机将抛出一个OutOfMemoryError
2、异常代码构造public class PermOutOfMemory { public void call() { int i = 0; try{ URL url = new File("/work/apps/apache-tomcat-8.0.37/webapps/sample/WEB-INF/classes").toURL(); List<ClassLoader> classLoaderList = new ArrayList<ClassLoader>(); while(true) { //不同的classloader具有隔离功能,模拟加载无穷多个类 ClassLoader classLoader = new URLClassLoader(new URL[]{url}); classLoader.loadClass("com.sample.oom.HeapOutOfMemory"); classLoaderList.add(classLoader); //webappClassLoader.close(); i++; } }catch(Throwable e) { System.out.println("call i is: " + i); e.printStackTrace(); } } }
2.1、设置OPTS参数
JAVA_OPTS=" -Xmx50m -Xms50m -Xmn20m -Xss160k -XX:MetaspaceSize=50m -XX:MaxMetaspaceSize=50m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/work/doc/logs"
2.2、异常查看
3、异常分析,可以参考heap的异常,使用jhat命令,找到耗费内存较多的class,查看对其引用的class,找到业务代码,此处就不做讲解 - 线程创建溢出
1、创建线程时候溢出,线程对象是由堆内存管理,同时会消耗一部分物理内存,当可供消耗的内存或者系统支持创建的线程达到最大限制时出现给溢出
2、溢出代码构造public void call() { int i = 0; boolean isStop = false; while(!isStop) { try { Runnable runnable = new NativeThread(); Thread thread = new Thread(runnable); thread.start(); i++; }catch(Throwable e) { e.printStackTrace(); isStop = true; System.out.println("create thread is: " + i); } } } class NativeThread implements Runnable{ /* * * * @see java.lang.Runnable#run() */ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
3、溢出提示:
4、溢出分析
1.本地支持的物理内存
2.ulimit支持的最大线程数
3.Xss线程栈的大小
4.java堆内存大小
与以上参数有关,降低3和4,则有助于提高所支持的线程创建数 - 内存回收耗时溢出
GC耗费大量时间去回收极小的空间时会发生的一种溢出,官方的解释花费超过98%的时间去回收2%的堆空间时,会抛出该异常
‘if too much time is being spent in garbage collection: if more than 98% of the total time is spent in garbage collection and less than 2% of the heap is recovered, an OutOfMemoryError will be thrown.’
1、代码构造:
2、设置Java启动参数public void call() { int i = 0; try{ URL url = new File("/work/apps/apache-tomcat-8.0.37/webapps/sample/WEB-INF/classes").toURL(); List<ClassLoader> classLoaderList = new ArrayList<ClassLoader>(); while(true) { //不同的classloader具有隔离功能,模拟加载无穷多个类 ClassLoader classLoader = new URLClassLoader(new URL[]{url}); classLoader.loadClass("com.xiaojukeji.sample.oom.HeapOutOfMemory"); classLoaderList.add(classLoader); //webappClassLoader.close(); i++; } }catch(Throwable e) { System.out.println("call i is: " + i); e.printStackTrace(); } }
JAVA_OPTS=" -Xmx50m -Xms50m -Xmn10m -Xss1m -XX:MetaspaceSize=500m -XX:MaxMetaspaceSize=1000m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/work/doc/logs"
3、溢出提示
4、溢出分析
4.1避免重复创建和加载class
4.2设置合理的heap大小
4.3目的就是降低GC频率和次数
下面是该程序调用中,数秒内,多次的GC回收,如下:S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 10.49 28.11 42.18 8.57 40 0.141 53 5.394 5.535