Java JVM 3-内存区域OOM异常以及和StackOverFlowError的区别

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_40739833/article/details/80715047

 上一节我们讲过,内存区域除了线程私有的程序计数器区之外,都存在OOM。

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

 关于虚拟机栈会产生的两种异常:

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,会抛出StackOverFlow内存溢出异常。

  • 如果虚拟机在拓展栈时无法申请到足够的内存空间,则会抛出OOM泄露异常。

 栈上主要发生的是StackOverFlowError,所以我们对其进行分析。

 出现StackOverflowError异常时有错误堆栈信息可以阅读,可以直观的找到问题所在。如果使用虚拟机默认运行参数,栈的深度对于正常的方法调用(包括递归),完全够用。我们可以通过修改运行参数 -Xss 将栈的容量变小来观察异常的出现。

 观察单线程StackOverFlowError的例子。

/**
 * 修改运行参数为:-Xss256K
 * 方便观察效果
 */
public class Test {
    private int stackLen = 1;
    // 没有出口的递归
    public void leak() {
        stackLen++;
        leak();
    }

    public static void main(String[] args) {
        Test test = new Test();
        try {
            test.leak();
        } catch (Throwable e) {
            throw e;
        }
    }

}

运行结果:

StackOverFlowError

 观察多线程下内存溢出:

public class Test {

    // 死循环
    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) {
        Test test = new Test();
        test.stackLeakByThread();
    }
}

运行结果:

运行结果

 哪位同学要是能坚持到蓝屏之前杀掉这个进程,请留言。

 如果是因为多线程导致的内存溢出问题,在不能减少线程数的情况下,只能减少最大堆和减少栈容量的方式来换取更多线程。

2. java堆溢出

 Java堆用于存储对象实例,所以只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免GC清除这些对象,那么在对象数量达到最大堆容量后就会产生内存泄露异常(OOM)。

 上节中已经讲到了,可以通过设置JVM运行参数-Xms:设置堆的最小值、-Xmx:设置堆最大值来使程序的最大堆容量等于最小堆容量,这样java堆就不能扩容了。下面我们来看一个Java堆OOM的测试。

 观察Java Heap OOM:

import java.util.ArrayList;
import java.util.List;

/**
 * -Xmx20m 堆最大容量
 * -Xms20m 堆最小容量,最大最小相同,使堆不能自动扩容
 * -XX:+HeapDumpOnOutOfMemoryError 当发生了OOM,会将异常信息进行dump并进行简单异常信息的打印
 */

public class Test {

    static class OOM {

    }

    public static void main(String[] args) {
        List<OOM> list = new ArrayList<>();
        // 死循环创建对象实例,将堆空间耗尽
        while (true) {
            list.add(new OOM());
        }
    }
}

运行结果:

运行结果

 Java堆内存的OOM异常是实际应用中最常见的内存泄露情况。

 当出现Java堆内存泄露时,异常堆栈信息”java.lang.OutOfMemoryError”会进一步提示”Java heap space”。

 当出现”Java heap space”则很明确的告知我们,OOM发生在堆上。此时要对Dump出来的文件进行分析,分析OOM的产生到底是出现了内存泄漏(MemoryLeak)还是内存溢出(Memory Overflow)。

3. 内存泄露与内存溢出的区别

 内存泄漏 : 泄漏对象无法被GC。(StackOverFlowError为典型的内存泄露)

 内存溢出 : 内存对象确实还应该存活。此时要根据JVM堆参数与物理内存相比较检查是否还应该把JVM堆内存调大;或者检查对象的生命周期是否过长。(OOM为典型的内存溢出)

 发生了异常之后,如果将对的容量扩充一倍,还没有解决问题,则该异常为内存泄漏,如果解决了,为内存溢出。

展开阅读全文

没有更多推荐了,返回首页