写代码实现堆溢出、栈溢出、永久代溢出、直接内存溢出

来源:互联网 发布:淘宝司法拍卖商铺税费 编辑:程序博客网 时间:2024/06/05 18:00
  1. 栈溢出(StackOverflowError)
  2. 堆溢出(OutOfMemoryError:Java heap space)
  3. 永久代溢出(OutOfMemoryError: PermGen space)
  4. 直接内存溢出

一、堆溢出

创建对象时如果没有可以分配的堆内存,JVM就会抛出OutOfMemoryError:java heap space异常。

堆溢出实例:

/** * VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError */public static void main(String[] args) {    List<byte[]> list = new ArrayList<>();    int i=0;    while(true){        list.add(new byte[5*1024*1024]);        System.out.println("分配次数:"+(++i));    }}运行结果:分配次数:1分配次数:2分配次数:3java.lang.OutOfMemoryError: Java heap spaceDumping heap to java_pid2464.hprof ...Heap dump file created [16991068 bytes in 0.047 secs]Exception in thread "main" java.lang.OutOfMemoryError: Java heap space    at com.ghs.test.OOMTest.main(OOMTest.java:16)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

附:dump文件会在项目的根目录下生成

从上面的例子我们可以看出,在进行第4次内存分配时,发生了内存溢出。

二、栈溢出

栈空间不足时,需要分下面两种情况处理:

  • 线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError
  • 虚拟机在扩展栈深度时无法申请到足够的内存空间,将抛出OutOfMemberError

附:当前大部分的虚拟机栈都是可动态扩展的。

1、栈空间不足——StackOverflowError实例

public class StackSOFTest {    int depth = 0;    public void sofMethod(){        depth ++ ;        sofMethod();    }    public static void main(String[] args) {        StackSOFTest test = null;        try {            test = new StackSOFTest();            test.sofMethod();        } finally {            System.out.println("递归次数:"+test.depth);        }    }}执行结果:递归次数:982Exception in thread "main" java.lang.StackOverflowError    at 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)……后续堆栈信息省略
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

我们可以看到,sofMethod()方法递归调用了982次后,出现了StackOverflowError。

2、栈空间不足——OutOfMemberError实例 
单线程情况下,不论是栈帧太大还是虚拟机栈容量太小,都会抛出StackOverflowError,导致单线程情境下模拟栈内存溢出不是很容易,不过通过不断的建立线程倒是可以产生内存溢出异常。

public class StackOOMTest {    public static void main(String[] args) {        StackOOMTest test = new StackOOMTest();        test.oomMethod();    }    public void oomMethod(){        while(true){            new Thread(new Runnable() {                @Override                public void run() {                    loopMethod();                }            }).start();;        }    }    private void loopMethod(){        while(true){        }    }}运行结果:……操作系统直接挂掉了
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

如果哪位大神能够成功模拟,还望指点一二。

三、永久代溢出

永久代溢出可以分为两种情况,第一种是常量池溢出,第二种是方法区溢出。

1、永久代溢出——常量池溢出 
要模拟常量池溢出,可以使用String对象的intern()方法。如果常量池包含一个此String对象的字符串,就返回代表这个字符串的String对象,否则将String对象包含的字符串添加到常量池中。

public class ConstantPoolOOMTest {    /**     * VM Args:-XX:PermSize=10m -XX:MaxPermSize=10m     * @param args     */    public static void main(String[] args) {        List<String> list = new ArrayList<>();        int i=1;        try {            while(true){                list.add(UUID.randomUUID().toString().intern());                i++;            }        } finally {            System.out.println("运行次数:"+i);        }    }}运行结果:……比较尴尬的是,通过intern,始终无法模拟出常量池溢出,我的猜想是JDK7对常量池做了优化。如果哪位大神成功模拟出来了,还望指点一二。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

找了好久,终于弄清楚了使用string.intern()方法无法模拟常量池溢出的原因。

因为在JDK1.7中,当常量池中没有该字符串时,JDK7的intern()方法的实现不再是在常量池中创建与此String内容相同的字符串,而改为在常量池中记录Java Heap中首次出现的该字符串的引用,并返回该引用。 
简单来说,就是对象实际存储在堆上面,所以,让上面的代码一直执行下去,最终会产生堆内存溢出。 
下面我将堆内存设置为:-Xms5m -Xmx5m,执行上面的代码,运行结果如下:

运行次数:58162Exception in thread "main" java.lang.OutOfMemoryError: Java heap space    at 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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2、永久代溢出——方法区溢出 
方法区存放Class的相关信息,下面借助CGLib直接操作字节码,生成大量的动态类。

public class MethodAreaOOMTest {    public static void main(String[] args) {        int i=0;        try {            while(true){                Enhancer enhancer = new Enhancer();                enhancer.setSuperclass(OOMObject.class);                enhancer.setUseCache(false);                enhancer.setCallback(new MethodInterceptor() {                    @Override                    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {                        return proxy.invokeSuper(obj, args);                    }                });                enhancer.create();                i++;            }        } finally{            System.out.println("运行次数:"+i);        }    }    static class OOMObject{    }}运行结果:运行次数:56Exception in thread "main" Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

虽然出现了异常,但是打印的堆栈信息似乎并不是我们想要的……

四、直接内存溢出

DirectMemory可以通过-XX:MaxDirectMemorySize指定,如果不指定,默认与Java堆的最大值(-Xmx指定)一样。 
NIO会使用到直接内存,你可以通过NIO来模拟,在下面的例子中,跳过NIO,直接使用UnSafe来分配直接内存。

public class DirectMemoryOOMTest {    /**     * VM Args:-Xms20m -Xmx20m -XX:MaxDirectMemorySize=10m     * @param args     */    public static void main(String[] args) {        int i=0;        try {            Field field = Unsafe.class.getDeclaredFields()[0];            field.setAccessible(true);            Unsafe unsafe = (Unsafe) field.get(null);            while(true){                unsafe.allocateMemory(1024*1024);                i++;            }        } catch (Exception e) {            e.printStackTrace();        }finally {            System.out.println("分配次数:"+i);        }    }}运行结果:Exception in thread "main" java.lang.OutOfMemoryError    at sun.misc.Unsafe.allocateMemory(Native Method)    at com.ghs.test.DirectMemoryOOMTest.main(DirectMemoryOOMTest.java:20)分配次数:27953
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

总结: 
栈内存溢出:程序所要求的栈深度过大。 
堆内存溢出: 分清内存泄露还是 内存容量不足。泄露则看对象如何被 GC Root 引用,不足则通过调大-Xms,-Xmx参数。 
永久代溢出:Class对象未被释放,Class对象占用信息过多,有过多的Class对象。 
直接内存溢出:系统哪些地方会使用直接内存。

最后,送大家一句话:觉知此事要躬行。 
很多时候,我们看别人提供的示例,都觉得蛮合理,但是实际运行起来,有可能并非如此。

阅读全文
0 0