美文网首页程序园Java BlogJava 杂谈
JVM学习笔记二【异常实战】

JVM学习笔记二【异常实战】

作者: 爪哇部落格 | 来源:发表于2019-05-05 22:03 被阅读1次

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

    演示环境如下:

    • 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输出

    Java堆

    dump文件分析

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

    dump

    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输出

    stack

    实验结果表明,无论是栈帧太大,或者栈内存空间过小,当无法分配内存时,虚拟机都会抛出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输出

    stack

    方法区: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输出

    Metaspace

    发现过程

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

    异常输出如下:


    类加载过程

    附录 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官方参数

    相关文章

      网友评论

        本文标题:JVM学习笔记二【异常实战】

        本文链接:https://www.haomeiwen.com/subject/ghlkoqtx.html