码字不易,欢迎大家转载,烦请注明出处;谢谢配合

演示环境如下:

  • JDK1.8

前文我们针对JVM运行时区域划分做了介绍,明确了各区域的作用,以及可能出现的异常,烦请思考以下问题:

什么样的代码会让指定区域反生OutOfMemoryError(OOME)或者StackOverflowError(SOE)呢?

这个问题你也许会感到困惑,为什么要去了解会出现OOME或者SOE的代码?实际上我们只有对OOME或者SOE有了清楚的认识,才能合理的避免问题的产生,以及发生问题时能够可以有效的解决。

Java堆:OutOfMemoryError

首先在“构建”Java堆的OutOfMemoryError时,我们应对Java堆有充足的认识,只有知道堆的作用,才能知道为何Java堆会发生OutOfMemoryError;Java堆中存储的是对象的实例,这也是我们的模拟此问题的出发点,“构建”Java堆的OutOfMemoryError参考以下代码示例:

代码调试

调整虚拟机启动参数:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError

package sunce.xin.education.jvm;

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

/**
 * Java堆OutOfMemoryError
 * @author lowrie
 * @date 2019-05-05
 */
public class HeapErrorTest {

    public static void main(String[] args) {
        List<HeapErrorTest> list = new ArrayList<>();
        while (true) {
            list.add(new HeapErrorTest());
        }
    }
}

Error输出

image-1655134213495

dump文件分析

在发生OutOfMemoryError时我们可以导出dump文件,利用内存分析工具来定位问题产生的原因,笔者常用的内存分析工具是Jprofiler,如下是通过Jprofiler分析以上代码dump的示例:

image-1655134230563!

Java虚拟机栈:StackOverflowError

Java虚拟机栈是由线程创建用于描述Java方法的执行,栈的大小越大可执行的方法深度越深,为了方便“构建”StackOverflowError,我们修改启动参数如下:

代码调试

调整虚拟机启动参数:-Xss256k

package sunce.xin.education.jvm;

/**
 * 栈StackOverflowError
 * @author lowrie
 * @date 2019-05-05
 */
public class StackErrorTest {

    private int length = 0;

    public static void main(String[] args) {
        StackErrorTest object = new StackErrorTest();
        try {
            object.callMethod();
        } catch (Throwable e) {
            System.out.println("stack deep length:" + object.length);
            e.printStackTrace();
        }
    }

    public void callMethod() {
        while (true) {
            length += 1;
            callMethod();
        }
    }
}

Error输出

image-1655134250107

实验结果表明,无论是栈帧太大,或者栈内存空间过小,当无法分配内存时,虚拟机都会抛出StackOverflowError.

Java虚拟机栈:OutOfMemoryError

我们知道当虚拟机扩展栈时,如果无法申请到足够的内存空间,则会抛出OutOfMemoryError;操作系统分配给每个进程的内存空间是有限的,假如你的操作系统内存只有2g,减去分配给堆的Xmx(最大堆大小),再减去分配给元数据的MaxMetaspaceSize/MaxPermSize,程序计数器占用的空间很小可以忽略,剩余的就被Java虚拟机栈和本地方法栈瓜分,所以每个栈空间分配的越大,所能创建的线程数越少。

代码调试

调整虚拟机启动参数:-Xss2m

package sunce.xin.education.jvm;

/**
 * 栈空间无法分配导致的内存溢出
 * 虚拟机参数 -Xss2m
 *
 * @author lowrie
 * @date 2019-05-06
 */
public class StackCauseOutOfMemoryErrorTest {

    private void stackLeak() {
        while (true) {
        }
    }

    private void currentTest() {
        while (true) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    stackLeak();
                }
            });
            thread.start();
        }
    }

    public static void main(String[] args) {
        StackCauseOutOfMemoryErrorTest test = new StackCauseOutOfMemoryErrorTest();
        test.currentTest();
    }
}

Error输出

image-1655134269770

方法区:OutOfMemoryError

我们知道方法区(元数据区)是用来保存类的描述信息,以及常量池等信息;所以“构建”此区域的OutOfMemoryError,出发点也是针对其保存的信息;我们通过以下实例来构建:

代码调试

调整虚拟机启动参数:
JDK1.8 -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
JDK1.7 -XX:PermSize=10m -XX:MaxPermSize=10m

package sunce.xin.education.jvm;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * Meta/Method Area OutOfMemoryError
 * JDK1.8 -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
 *
 * @author lowrie
 * @date 2019-05-05
 */
public class MetaErrorTest {

    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(MetaErrorTest.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invokeSuper(o, objects);
                }
            });
            enhancer.create();
        }
    }

}

Error输出

image-1655134313496

发现过程

添加**-verbose:class**参数,观察class的加载过程
-verbose:class -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m

异常输出如下:
image-1655134331338

附录 JVM常用参数

参数 示例 说明
-Xsssize -Xss1m 设置线程栈大小
-Xmssize -Xms1m 设置堆初始大小
-Xmxsize -Xmx1m 设置堆最大大小
-Xmnsize -Xmn1m 设置新生代最大大小
-XX:NewSize=size -XX:NewSize=10m 设置新生代初始大小
-XX:MetaspaceSize=size -XX:MetaspaceSize=10m 设置元数据区大小
-XX:MaxMetaspaceSize=size -XX:MaxMetaspaceSize=10m 设置最大元数据区大小
-verbose:class -verbose:class 显示类加载信息
-verbose:gc -verbose:gc 显示GC信息

参考oracle官方参数