资讯专栏INFORMATION COLUMN

论JVM爆炸的几种姿势及自救方法

hyuan / 595人阅读

摘要:方法区溢出在的方法区中,它主要存放了类的信息,常量,静态变量等。运行结果简单解决思路一般来说此类问题多出现在存在递归的地方,要从代码里重新审视递归未结束的原因,若递归的方法没问题可以根据实际情况调整参数的大小。

前言

如今不管是在面试还是在我们的工作中,OOM总是不断的出现在我们的视野中,所以我们有必要去了解一下导致OOM的原因以及一些基本的调整方法,大家可以通过下面的事例来了解一下什么样的代码会导致OOM,帮助我们以后在工作中能够通过异常信息来判断是JVM里面哪个区域出现了问题。

先介绍一下笔者的相关编码环境。

jdk:java version "1.8.0_121"

ide:IntelliJ IDEA 2019.1 (Community Edition)

正文
1.Java堆溢出

Java中的堆存储的都是对象实例,当我们不断的创建对象,而GC的时候又不能回收,当存储的对象大小超过了-Xmx的值,这时候则会出现OutOfMemoryError.[-XX:+HeapDumpOnOutOfMemoryError]参数可以让jvm出现内存溢出的时候dump出内存堆转储快照。

/**
 * VM Args: -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
 * @author wangzenghuang
 */
public class HeapOOMDemo {
    public static void main(String[] args) {
        List stringList = new ArrayList<>();
        while(true){
            stringList.add("str");
        }
    }
}

运行结果,发生OOM,并且在我们项目的根目录dump出当前的内存堆快照

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid1376.hprof ...
Heap dump file created [7972183 bytes in 0.047 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3210)
    at java.util.Arrays.copyOf(Arrays.java:3181)
    at java.util.ArrayList.grow(ArrayList.java:261)
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
    at java.util.ArrayList.add(ArrayList.java:458)
    at HeapOOMDemo.main(HeapOOMDemo.java:12)

Process finished with exit code 1

简单解决思路

那么发生这个问题以后我们的解决思路有哪些呢?我们可以利用一些工具(例如Eclipse Memory Analyzer
)来分析dump出的文件,一般来说,当生产环境发生OOM,比较常见的一个原因是发生了内存泄漏,用工具可以分析出泄露的对象到GC Root的引用链,从而定位到问题代码。假如经过分析后发现内存中的对象都是“必须存活”的对象,这时候就要思考下项目中是否把“-Xms跟-Xmx”设置得太小了(当然这里也不是随意调大,需要结合机器的物理内存情况),再者需要留意代码中是否有一些长生命周期的对象,从代码中优化内存消耗。

2.方法区溢出

在jvm的方法区中,它主要存放了类的信息,常量,静态变量等。在jdk8以前是通过“-XX:PermSize,-XX:MaxPermSize”来调整这个区域的值,但是从8开始呢,永久代的概念被MetaSpace(元空间)代替了,对应的参数也变成了“-XX:MetaspaceSize,-XX:MaxMetaspaceSize”。在这个例子中使用CGLib来动态生成一些类,方便我们实验操作。

/**
 * VM Args: -XX:MetaspaceSize=5m -XX:MaxMetaspaceSize=5m
 * @author wangzenghuang
 */
public class MethodAreaOOMDemo {
    public static void main(String[] args) {
        while(true){
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invokeSuper(obj,objects);
                }
            });
            enhancer.create();
        }
    }
    static class OOMObject{}
}

运行结果

Exception in thread "main" java.lang.OutOfMemoryError: Metaspace

简单解决方法

这个问题的话,一般来说根据情况调整方法区的大小就行了,网上也有人说可以去掉MetaSpace的的大小限制,但是不建议这么干,毕竟不可控的事情我们要少点干,很容易给自己埋雷。

3.栈溢出

对于我们来说,还有一个熟悉的错误,那就是“StackOverflowError”,它是由线程请求的栈深度超过了jvm允许的最大范围而产生的。“-Xss”参数可以设置栈容量。

/**
 * VM Args: -Xss128k
 * @author wangzenghuang
 */
public class StackOFDemo {
    private static int stackLength = 1;

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

    public static void main(String[] args) {
        StackOFDemo stackOFDemo = new StackOFDemo();
        try {
            stackOFDemo.stackLeak();
        }catch (Throwable e){
            System.out.println("length : "+ stackLength);
            throw e;
        }
    }
}

运行结果

length : 983
Exception in thread "main" java.lang.StackOverflowError
    at StackOFDemo.stackLeak(StackOFDemo.java:10)
    at StackOFDemo.stackLeak(StackOFDemo.java:10)
    ...

简单解决思路

一般来说此类问题多出现在存在递归的地方,要从代码里重新审视递归未结束的原因,若递归的方法没问题可以根据实际情况调整“-Xss”参数的大小。还有一些代码的循坏依赖也会造成此类情况,

4.直接内存溢出

本机直接内存默认与“-Xmx”设定的值一样大,可以通过“-XX:MaxDirectMemorySize”修改。

/**
 * VM Args: -Xmx20m -XX:MaxDirectMemorySize=10
 * @author wangzenghuang
 */
public class DirectMemoryOOMDemo {
    private static final int _1MB = 1024 * 1024;

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

运行结果

呃,一运行这段代码idea直接闪退了,查阅其他资料可以得知当DirectMemory导致内存溢出时,Heap Dump文件是很小的,如果程序中有使用NIO的情况可以检查一下。

总结

这里所展示的代码只是可以触发jvm的各种错误,但是并不代表这是唯一的触发错误的方方式,假如我们的代码比较复杂,有时候遇到类似错误的时候还是需要耐心分析。

文章内容首发于微信公众号《深夜里的程序猿》,转载请注明出处,侵权必究。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/77495.html

相关文章

  • Java日志正确使用姿势

    摘要:但是往往越简单的东西越容易让我们忽视,从而导致一些不该有的发生,作为一名严谨的程序员,怎么能让这种事情发生呢所以下面我们就来了解一下关于日志的那些正确使用姿势。级别表示出现了严重错误,程序将会中断执行。 前言 关于日志,在大家的印象中都是比较简单的,只须引入了相关依赖包,剩下的事情就是在项目中尽情的打印我们需要的信息了。但是往往越简单的东西越容易让我们忽视,从而导致一些不该有的bug发...

    UCloud 评论0 收藏0
  • 使用ConcurrentHashMap一定线程安全?

    摘要:前言老王为何半夜惨叫几行代码为何导致服务器爆炸说好的线程安全为何还是出问题让我们一起收看今天的走进正文出现背景说到的出现背景,还得从说起。在跟中,都只是调用的方法,各自都是原子操作,是线程安全的。 前言 老王为何半夜惨叫?几行代码为何导致服务器爆炸?说好的线程安全为何还是出问题?让我们一起收看今天的《走进IT》 正文 CurrentHashMap出现背景 说到ConcurrentHas...

    荆兆峰 评论0 收藏0
  • Java核心技术教程整理,长期更新

    以下是Java技术栈微信公众号发布的关于 Java 的技术干货,从以下几个方面汇总。 Java 基础篇 Java 集合篇 Java 多线程篇 Java JVM篇 Java 进阶篇 Java 新特性篇 Java 工具篇 Java 书籍篇 Java基础篇 8张图带你轻松温习 Java 知识 Java父类强制转换子类原则 一张图搞清楚 Java 异常机制 通用唯一标识码UUID的介绍及使用 字符串...

    Anchorer 评论0 收藏0
  • 聊聊Vue.js组件间通信几种姿势

    摘要:子组件向父组件通信方法一使用事件父组件向子组件传递事件方法,子组件通过触发事件,回调给父组件。非父子组件兄弟组件之间的数据传递非父子组件通信,官方推荐使用一个实例作为中央事件总线。 写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出。 文章的原地址:https://github.com/answ...

    Profeel 评论0 收藏0
  • 异步读取文件几种姿势

    摘要:臆想的针对读取到的内容进行操作,比如打印文件内容臆想中,读取文件是有返回值的,将返回值,即文件内容,赋给一个变量,然后决定对读取到的内容进行相应的操作,例如打印文件中的内容。 臆想的 let fs = require(fs) function readFile(filename){ ... } let content = readFile(config.js) // 针对读...

    chinafgj 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<