JVM实战-OutOfMemoryError异常

我将通过代码实例验证java内存各个区域(除了程序计数器)出现OutOfMemoryError异常的场景。

1.Java堆溢出

/**堆溢出:通过不断创建无法被回收的对象实例
 * VM Args:-Xms16M(堆初始) -Xmx16M(堆最大) -Xmn16M(新生代) -XX:+PrintGCDetails -XX:SurvivorRatio=8
 *          -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=E:\java\java_log\heap_dump
 * @author zsc
 */
public class HeapOOM {
    static class OOMObject {
    }

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();

        while (true) {
            list.add(new OOMObject());
        }
    }
}

运行后出现异常:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to E:\java\java_log\heap_dump\java_pid9548.hprof ...
Heap dump file created [19186297 bytes in 0.053 secs]

使用jvisualvm打开dump文件,可以看到对象实例OOMObject占满了空间:
在这里插入图片描述
分析堆的OOM异常时,要确认是出现内存泄漏还是内存溢出

内存溢出内存泄漏
出现场景申请内存时内存不够了申请的内存无法释放,编码存在问题,持有了无用对象没释放
解决方法检查堆参数-Xmx,-Xms看是否可调大查看泄漏对象到GC Roots的引用链

2.虚拟机栈和本地方法栈溢出

  • HotSpot虚拟机不区分虚拟机栈和本地方法栈,所以-Xoss(本地方法栈大小)参数无效,栈容量由-Xss设定。
  • 如果线程请求的栈深度大于虚拟机允许的,将抛出StackOverflowError
  • 如果扩展栈时申请不到足够空间,将抛出OutOfMemoryError
/**栈溢出测试:通过递归耗尽栈,出现StackOverflowError异常
 * VM Args:-Xss128k
 * @author zsc
 */
public class StackSOF {
    private int stackLength = 1;

    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) {
        StackSOF sof = new StackSOF();
        try {
            sof.stackLeak();
        } catch (Throwable e) {
            System.out.print("栈深度:" + sof.stackLength);
            throw e; //抛出,不能把异常吃了
        }
    }
}

执行结果:

栈深度:9823 actionable tasks: 2 executed, 1 up-to-date
Exception in thread "main" java.lang.StackOverflowError
	at com.zsc.jvm.oom.StackSOF.stackLeak(StackSOF.java:10)
  • 单线程下,栈帧太大还是虚拟机栈容量太小,都是抛出StackOverflowError
  • 创建多线程时,如果给每个线程分配的栈内存较大 ,容易出现OutOfMemoryError内存溢出异常。(如32位系统内存为2G,减去Xmx,MaxPermSize等,剩下的已不多)
/**创建多个线程(不断分配所需栈内存)导致内存溢出
 * 注意 :运行前保存好工作,windows可能假死(Java线程是映射到内核线程上的)
 * VM Args:-Xss16m (设大些)
 * @author zsc
 */
public class StackOOM {
    private void donStop() { while (true) {} }

    public void stackLeakByThread() {
        while (true) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    donStop();
                }
            });
            thread.start();
        }
    }

    public static void main(String[] args) {
        StackOOM oom = new StackOOM();
        oom.stackLeakByThread();
    }
}

3.方法区和运行时常量池溢出

3.1 运行时常量池

运行时常量池是方法区的一部分

/**运行时常量池的内存溢出
 *
 * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M 使用JDK1.6进行测试才会出现OOM:PermGen space
 *          -Xms20m -Xmx20m 减少堆大小
 *          -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=E:\java\java_log\heap_dump
 * @author zsc
 */
public class RuntimeConstantPoolOOM {
    public static void main(String[] args) {
        //使用list保持常量池中字符串的引用,避免被GC回收
        List<String> list = new ArrayList<>();
        int i  = 0; //10m的PermSize在integer范围内足够产生OOM
        while (true) {
            list.add(String.valueOf(i++).intern());
        }
    }
}
  • JDK1.6前常量池在永久代内,以上测试要在JDK1.6才会出现OOM:PermGen space。
  • String.intern()是一个native方法:如果字符串常量池中已有该字符串,返回;否则加到常量池,返回引用。
  • JDK1.7字符串常量池移到堆中,运行时常量池剩下的还在方法区。
  • JDK1.8用元空间(Metaspace)取代永久代实现方法区,字符串常量池依然在堆,运行时常量池还在方法区。
  • 因此,以上代码实例运行结果:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
	at java.lang.Integer.toString(Integer.java:403)
	at java.lang.String.valueOf(String.java:3099)
	at com.zsc.jvm.oom.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:19)

GC overhead limit exceeded表示:执行垃圾收集的时间比例太大, 有效的运算量太小. 默认情况下, 如果GC花费的时间超过 98%, 并且GC回收的内存少于 2%, JVM就会抛出这个错误。

3.2 方法区

/**通过CGLib大量动态生成Class,使方法区内存溢出
 *
 * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M  需要JDK1.7以前,才出现PermGen space不足
 *  JDK1.8元空间实现的方法区只受本地内存限制
 * @author zsc
 */
public class MethodAreaOOM {
    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invoke(o, args);
                }
            });
            enhancer.create();
        }
    }
    static class OOMObject {
    }
}

4.直接内存溢出

-XX:MaxDirectMemorySize指定直接内存大小,不指定时默认与Java堆最大值(-Xmx)一样。
测试用例:

/**
 * VM Args:-Xms16M(堆初始) -Xmx16M(堆最大) -XX:MaxDirectMemorySize=10M
 * @author zsc
 */
public class DirectMemoryOOM {
    private static final int _1M = 1024 * 1024;

    public static void main(String[] args) throws IllegalAccessException {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true) {
            unsafe.allocateMemory(_1M);
        }

    }
}

执行结果:

Exception in thread "main" java.lang.OutOfMemoryError
	at sun.misc.Unsafe.allocateMemory(Native Method)
	at com.zsc.jvm.oom.DirectMemoryOOM.main(DirectMemoryOOM.java:20)

直接内存溢出时,在Heap dump文件中不会看见明显的异常。Dump较小

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值