欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-15 14:38:11
...

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

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

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

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

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

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

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

 观察单线程*Error的例子。

/**
 * 修改运行参数为:-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;
        }
    }

}

运行结果:

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

 观察多线程下内存溢出:

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();
    }
}

运行结果:

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

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

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

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 JVM 3-内存区域OOM异常以及和*Error的区别

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

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

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

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

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

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

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