論JVM爆炸的幾種姿式及自救方法

前言

現在不論是在面試仍是在咱們的工做中,OOM老是不斷的出如今咱們的視野中,因此咱們有必要去了解一下致使OOM的緣由以及一些基本的調整方法,你們能夠經過下面的事例來了解一下什麼樣的代碼會致使OOM,幫助咱們之後在工做中可以經過異常信息來判斷是JVM裏面哪一個區域出現了問題。java

先介紹一下筆者的相關編碼環境。面試

jdk:java version "1.8.0_121"微信

ide:IntelliJ IDEA 2019.1 (Community Edition)jvm


正文

1.Java堆溢出

Java中的堆存儲的都是對象實例,當咱們不斷的建立對象,而GC的時候又不能回收,當存儲的對象大小超過了-Xmx的值,這時候則會出現OutOfMemoryError.[-XX:+HeapDumpOnOutOfMemoryError]參數可讓jvm出現內存溢出的時候dump出內存堆轉儲快照。ide

/**
 * VM Args: -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
 * @author wangzenghuang
 */
public class HeapOOMDemo {
    public static void main(String[] args) {
        List<String> 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來動態生成一些類,方便咱們實驗操做。idea

/**
 * 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{}
}

運行結果spa

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的各類錯誤,可是並不表明這是惟一的觸發錯誤的方方式,假如咱們的代碼比較複雜,有時候遇到相似錯誤的時候仍是須要耐心分析。

文章內容首發於微信公衆號《深夜裏的程序猿》,轉載請註明出處,侵權必究。

相關文章
相關標籤/搜索