在JAVA虚拟机规范说明中,除了程序计数器不会出现OutOfMemoryError(简称OOM),其他几个内存区域都有可能发生OOM异常。
JAVA堆溢出
JAVA堆用于存储对象实例,只要不断的new对象,并且保证GC roots到对象之间有可达路径来避免垃圾回收这些对象,在对象达到一定的数量之后,就会产生OOM异常。
示例:
-
使用JVM参数限制JAVA堆的大小
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/var/log/gc_dump.dump
-
JAVA程序代码示例:
public class TestOOM{ static class OOMObejct{ } public static void main(String[] args){ List<OOMObejct> list = new ArrayList<>(); while(true){ list.add(new OOMObejct()); } } }
-
运行结果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3210) at java.util.Arrays.copyOf(Arrays.java:3181) at java.util.ArrayList.grow(ArrayList.java:265) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231) at java.util.ArrayList.add(ArrayList.java:462) at net.admol.jingling.demo.jvm.TestOOM.main(TestOOM.java:19)
当异常信息出现“java.lang.OutOfMemoryError”,并且后面有提示”Java heap space”时,就是JAVA堆出现了内存溢出。
栈溢出
由于虚拟机中并不区分虚拟机栈和本地方法栈, 栈的容量通过JVM参数 -Xss
设置,栈异常有两种可能异常:
- 线程的栈请求深度大于虚拟机所允许的最大深度时,会抛出StackOverFlowError异常
- 虚拟机栈在扩展栈时无法申请到足够的空间,会抛出OutOfMemoryError异常
异常示例:
-
JVM参数限制栈大小
-Xss128k
-
JAVA程序代码示例:
public class TestJVMStackOME{ private int length = 1; public void stackLeak(){ length++; stackLeak(); } public static void main(String[] args){ TestJVMStackOME test = new TestJVMStackOME(); try{ test.stackLeak(); }catch(Exception e){ System.out.println("stack length:"+test.length); throw e; } } }
-
运行结果:
Exception in thread "main" java.lang.StackOverflowError at net.admol.jingling.demo.jvm.TestJVMStackOME.stackLeak(TestJVMStackOME.java:11) at net.admol.jingling.demo.jvm.TestJVMStackOME.stackLeak(TestJVMStackOME.java:12) ......省略更多的输出
方法区溢出
因为运行时常量池是方法区的一部分,所以它们抛出的异常都是一样的。
String.intern()
是一个Native方法,它的作用是:如果常量池中已经包含了一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String 对象包含的字符串添加到常量池中,并且返回此String对象的引用。
在JDK1.6及之前,由于常量池分配在永久代内,我们可以通过-XX:PermSize 和 -XX:MaxPermSize 参数来限制方法区大小,从而间接限制常量池的大小。
示例:
-
设置JVM参数限制方法区大小
XX:PermSize=5M -XX:MaxPermSize=5M
-
JAVA程序代码示例:
public class TestMethodOOM{ public static void main(String[] args){ List<String> list = new ArrayList<>(); int i = 100; while(true){ list.add((String.valueOf(i++).intern())); } } }
-
在jdk1.6及之前会提示
java.lang.OutOfMemoryError:PermGen space
因为jdk1.8移除了PermGen(永久代),替换成了元空间(Metaspace),所以1.7及之后都不会报错
直接堆外内存溢出
DirectMemory容量可通过-XX:MaxDirectMemory 指定,如果不指定,则默认和JAVA堆最大值一致。
溢出示例:
-
设置JVM参数
Xmx10m -XX:MaxDirectMemorySize=5M
-
JAVA程序代码示例
public class TestDirectMemoryOOM{ public static void main(String[] args) throws IllegalAccessException{ Field field = Unsafe.class.getDeclaredFields()[0]; field.setAccessible(true); Unsafe unsafe = (Unsafe)field.get(null); long _1M = 1024*1024; while(true){ unsafe.allocateMemory(_1M); } } }
-
运行结果
Exception in thread "main" java.lang.OutOfMemoryError at sun.misc.Unsafe.allocateMemory(Native Method) at net.admol.jingling.demo.jvm.TestDirectMemoryOOM.main(TestDirectMemoryOOM.java:18)
因为DirectMemory导致的内存溢出, 一个明显的特征是在Heap Dump文件中不会看见明显的异常, 如果发现dump的文件比较小, 而程序中又直接或间接使用了NIO,那就需要好好思考一下是否是DirectMemory这方面的原因了。