JVM运行时内存被划分成多个区域,而除了程序计数器之外,其他几个区都会出现OutOfMemoryError异常,主要原因就是对应内存区域的内存不足以再分配内存,一般要么是内存泄漏了要么就是内存参数设置的过小而导致。本文就在实际操作中模拟下JVM内存模型中各个区域出现内存溢出的场景。
1.堆内存溢出
先设置JVM启动参数,设置初始化堆内存大小为 -Xms15M -Xmx15M
堆内存中主要存储对象实例,所以测试堆内存溢出就需要不断的创建对象实例,并且保证这些对象实例不被垃圾回收,测试代码如下
1 public static void main(String[] args) 2 { 3 heapErrorTest(); 4 } 5 6 public static void heapErrorTest() 7 { 8 List<User> userList = new ArrayList<>(); 9 int i = 0; 10 try 11 { 12 while (true) 13 { 14 i++; 15 System.out.println(i); 16 userList.add(new User()); 17 } 18 } 19 catch (Exception e) 20 { 21 System.out.println("成功创建user对象个数为:"+i); 22 e.printStackTrace(); 23 } 24 }
运行结果如下示:
1 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 2 at java.util.Arrays.copyOf(Arrays.java:3210) 3 at java.util.Arrays.copyOf(Arrays.java:3181) 4 at java.util.ArrayList.grow(ArrayList.java:261) 5 at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235) 6 at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) 7 at java.util.ArrayList.add(ArrayList.java:458) 8 at com.lucky.test.jvmtest.JVMTest.heapErrorTest(JVMTest.java:27) 9 at com.lucky.test.jvmtest.JVMTest.main(JVMTest.java:14)
此时报了OutOfMemoryError异常,也提示了是Java堆内存空间报的,但是是无法确定是什么原因导致的,也不知道具体是什么对象导致的,测试用例比较简单,而复杂的线上环境中就很难排查,所以需要让JVM在出现内存溢出的时候Dump出当前的内存堆转储快照,当出现异常后就可以使用其他工具来进行分析。针对上例,修改启动参数,添加 -XX: +HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=C:/Users/admin/Desktop/jvmdump/heapdump.hprof
运行结果为:
1 java.lang.OutOfMemoryError: Java heap space 2 Dumping heap to C:/Users/admin/Desktop/jvmdump/heapdump.hprof ... 3 Heap dump file created [24643218 bytes in 0.071 secs] 4 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 5 at java.util.Arrays.copyOf(Arrays.java:3210) 6 at java.util.Arrays.copyOf(Arrays.java:3181) 7 at java.util.ArrayList.grow(ArrayList.java:261) 8 at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235) 9 at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) 10 at java.util.ArrayList.add(ArrayList.java:458) 11 at com.lucky.test.jvmtest.JVMTest.heapErrorTest(JVMTest.java:27) 12 at com.lucky.test.jvmtest.JVMTest.main(JVMTest.java:14)
分析dump文件可以使用Eclipse的Memory Analysis插件,安装过程是打开Eclipse Help-》Eclipse Marketplace 然后搜索memory,然后直接安装即可
2.虚拟机栈内存溢出和栈溢出
虚拟机栈有栈溢出和内存溢出两种异常情况,栈溢出(StackOverFlowError)是在栈深度大于虚拟机设置的最大深度时会报出,内存溢出则会在内存不足时报出
栈溢出案例代码如下:
1 public static void stackOverFlowErrorTest() 2 { 3 // 栈溢出栈的深度过长导致,可以用递归方法在模拟 4 System.out.println("当前深度为:" + (i++)); 5 stackOverFlowErrorTest(); 6 }
运行结果为:
当前深度为:7958 当前深度为:7959 当前深度为:7960 当前深度为:7961 当前深度为:7962 当前深度为:7963 Exception in thread "main" java.lang.StackOverflowError at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:449) at java.lang.StringBuilder.append(StringBuilder.java:136) at java.lang.StringBuilder.<init>(StringBuilder.java:113) at com.lucky.test.jvmtest.JVMTest.stackOverFlowErrorTest(JVMTest.java:43) at com.lucky.test.jvmtest.JVMTest.stackOverFlowErrorTest(JVMTest.java:44) at com.lucky.test.jvmtest.JVMTest.stackOverFlowErrorTest(JVMTest.java:44)
内存溢出模拟案例如下:
1 public static void stackOutOfMemoryTest(){ 2 while(true){ 3 new Thread(new Runnable() 4 { 5 6 @Override 7 public void run() 8 { 9 dontStop(); 10 } 11 }).start(); 12 } 13 } 14 15 public static void dontStop(){ 16 while(true){ 17 System.out.println(Thread.currentThread().getName()); 18 } 19 }
通过不停的创建新线程,执行一个执行不完的方法,当到达一定数量时,内存就会不足以创建新线程。
结果为: java.lang.OutOfMemoryError: Java heap space
4.方法区内存溢出
方法区主要存放Class信息、以及静态常量,正常情况下方法区存储的内容是很少变动的,因为都是静态的内容,所以出现方法区内存溢出的情况也很少,但是有很多主流框架如Spring、Hibernate框架会对类进行增强,通过直接操作字节码,会生成大量的动态类,这时就会有大量的动态类需要存储在方法区,如果方法区的内存不足,也会出现内存溢出的情况。先设置JVM方法区内存大小 -XX:PermSize=10M -XX:MaxPermSize=10M
案例代码如下:
1 public static void permGenErrorTest(){ 2 while(true){ 3 Enhancer enhancer = new Enhancer(); 4 enhancer.setSuperclass(User.class); 5 enhancer.setUseCache(false); 6 enhancer.setCallback(new MethodInterceptor() 7 { 8 9 @Override 10 public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) 11 throws Throwable 12 { 13 return arg3.invokeSuper(arg0, arg2); 14 } 15 }); 16 enhancer.create(); 17 } 18 }
最终抛出异常:Caused by:java.lang.OutOfMemoryError:PermGen Space