堆溢出、栈溢出、永久代溢出、直接内存溢出
- 栈溢出(StackOverflowError)
- 堆溢出(OutOfMemoryError:Java heap space)
- 永久代溢出(OutOfMemoryError: PermGen space)
- 直接内存溢出
一、堆溢出
创建对象时如果没有可以分配的堆内存,JVM就会抛出OutOfMemoryError:java heap space异常。
堆溢出实例:
/*** VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError*/
public static void main(String[] args) {List list &#61; new ArrayList<>();int i&#61;0;while(true){list.add(new byte[5*1024*1024]);System.out.println("分配次数&#xff1a;"&#43;(&#43;&#43;i));}
}运行结果&#xff1a;
分配次数&#xff1a;1
分配次数&#xff1a;2
分配次数&#xff1a;3java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid2464.hprof ...
Heap dump file created [16991068 bytes in 0.047 secs]Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat com.ghs.test.OOMTest.main(OOMTest.java:16)
附&#xff1a;dump文件会在项目的根目录下生成
从上面的例子我们可以看出&#xff0c;在进行第4次内存分配时&#xff0c;发生了内存溢出。
二、栈溢出
栈空间不足时&#xff0c;需要分下面两种情况处理&#xff1a;
线程请求的栈深度大于虚拟机所允许的最大深度&#xff0c;将抛出StackOverflowError
虚拟机在扩展栈深度时无法申请到足够的内存空间&#xff0c;将抛出OutOfMemberError
附&#xff1a;当前大部分的虚拟机栈都是可动态扩展的。
1、栈空间不足——StackOverflowError实例
public class StackSOFTest {int depth &#61; 0;public void sofMethod(){depth &#43;&#43; ;sofMethod();}public static void main(String[] args) {StackSOFTest test &#61; null;try {test &#61; new StackSOFTest();test.sofMethod();} finally {System.out.println("递归次数&#xff1a;"&#43;test.depth);}}
}执行结果:
递归次数&#xff1a;982
Exception in thread "main" java.lang.StackOverflowErrorat com.ghs.test.StackSOFTest.sofMethod(StackSOFTest.java:8)at com.ghs.test.StackSOFTest.sofMethod(StackSOFTest.java:9)at com.ghs.test.StackSOFTest.sofMethod(StackSOFTest.java:9)
……后续堆栈信息省略
2、栈空间不足——OutOfMemberError实例
单线程情况下&#xff0c;不论是栈帧太大还是虚拟机栈容量太小&#xff0c;都会抛出StackOverflowError&#xff0c;导致单线程情境下模拟栈内存溢出不是很容易&#xff0c;不过通过不断的建立线程倒是可以产生内存溢出异常。
public class StackOOMTest {public static void main(String[] args) {StackOOMTest test &#61; new StackOOMTest();test.oomMethod();}public void oomMethod(){while(true){new Thread(new Runnable() {&#64;Overridepublic void run() {loopMethod();}}).start();;}}private void loopMethod(){while(true){}}
}运行结果&#xff1a;
……操作系统直接挂掉了
三、永久代溢出
永久代溢出可以分为两种情况&#xff0c;第一种是常量池溢出&#xff0c;第二种是方法区溢出。
1、永久代溢出——常量池溢出
要模拟常量池溢出&#xff0c;可以使用String对象的intern()方法。如果常量池包含一个此String对象的字符串&#xff0c;就返回代表这个字符串的String对象&#xff0c;否则将String对象包含的字符串添加到常量池中。
public class ConstantPoolOOMTest {/*** VM Args:-XX:PermSize&#61;10m -XX:MaxPermSize&#61;10m* &#64;param args*/public static void main(String[] args) {List list &#61; new ArrayList<>();int i&#61;1;try {while(true){list.add(UUID.randomUUID().toString().intern());i&#43;&#43;;}} finally {System.out.println("运行次数&#xff1a;"&#43;i);}}
}运行结果:
……比较尴尬的是&#xff0c;通过intern&#xff0c;始终无法模拟出常量池溢出&#xff0c;我的猜想是JDK7对常量池做了优化。
如果哪位大神成功模拟出来了&#xff0c;还望指点一二。
找了好久&#xff0c;终于弄清楚了使用string.intern()方法无法模拟常量池溢出的原因。
因为在JDK1.7中&#xff0c;当常量池中没有该字符串时&#xff0c;JDK7的intern()方法的实现不再是在常量池中创建与此String内容相同的字符串&#xff0c;而改为在常量池中记录Java Heap中首次出现的该字符串的引用&#xff0c;并返回该引用。
简单来说&#xff0c;就是对象实际存储在堆上面&#xff0c;所以&#xff0c;让上面的代码一直执行下去&#xff0c;最终会产生堆内存溢出。
下面我将堆内存设置为&#xff1a;-Xms5m -Xmx5m&#xff0c;执行上面的代码&#xff0c;运行结果如下&#xff1a;
运行次数&#xff1a;58162
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat java.lang.Long.toUnsignedString(Unknown Source)at java.lang.Long.toHexString(Unknown Source)at java.util.UUID.digits(Unknown Source)at java.util.UUID.toString(Unknown Source)at com.ghs.test.ConstantPoolOOMTest.main(ConstantPoolOOMTest.java:18)
2、永久代溢出——方法区溢出
方法区存放Class的相关信息&#xff0c;下面借助CGLib直接操作字节码&#xff0c;生成大量的动态类。
public class MethodAreaOOMTest {public static void main(String[] args) {int i&#61;0;try {while(true){Enhancer enhancer &#61; new Enhancer();enhancer.setSuperclass(OOMObject.class);enhancer.setUseCache(false);enhancer.setCallback(new MethodInterceptor() {&#64;Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {return proxy.invokeSuper(obj, args);}});enhancer.create();i&#43;&#43;;}} finally{System.out.println("运行次数&#xff1a;"&#43;i);}}static class OOMObject{}
}运行结果:运行次数&#xff1a;56
Exception in thread "main"
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
四、直接内存溢出
DirectMemory可以通过-XX:MaxDirectMemorySize指定&#xff0c;如果不指定&#xff0c;默认与Java堆的最大值(-Xmx指定)一样。
NIO会使用到直接内存&#xff0c;你可以通过NIO来模拟&#xff0c;在下面的例子中&#xff0c;跳过NIO&#xff0c;直接使用UnSafe来分配直接内存。
public class DirectMemoryOOMTest {/*** VM Args:-Xms20m -Xmx20m -XX&#xff1a;MaxDirectMemorySize&#61;10m* &#64;param args*/public static void main(String[] args) {int i&#61;0;try {Field field &#61; Unsafe.class.getDeclaredFields()[0];field.setAccessible(true);Unsafe unsafe &#61; (Unsafe) field.get(null);while(true){unsafe.allocateMemory(1024*1024);i&#43;&#43;;}} catch (Exception e) {e.printStackTrace();}finally {System.out.println("分配次数&#xff1a;"&#43;i);}}
}运行结果&#xff1a;
Exception in thread "main" java.lang.OutOfMemoryErrorat sun.misc.Unsafe.allocateMemory(Native Method)at com.ghs.test.DirectMemoryOOMTest.main(DirectMemoryOOMTest.java:20)
分配次数&#xff1a;27953
总结&#xff1a;
栈内存溢出&#xff1a;程序所要求的栈深度过大。
堆内存溢出&#xff1a; 分清内存泄露还是 内存容量不足。泄露则看对象如何被 GC Root 引用&#xff0c;不足则通过调大-Xms&#xff0c;-Xmx参数。
永久代溢出&#xff1a;Class对象未被释放&#xff0c;Class对象占用信息过多&#xff0c;有过多的Class对象。
直接内存溢出&#xff1a;系统哪些地方会使用直接内存。