深刻理解Java虛擬機之實戰OutOfMemoryError

Java虛擬機中哪些區域會發生OOM

堆、虛擬機棧、本地方法棧、方法區、直接內存java

堆OOM

測試程序:bash

/**
 * VM Args: -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
 * User:wangs
 * Date:2018/8/23
 */
public class HeapOOM {
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        while (true) {
            list.add(new Object());
        }
    }
}
複製代碼

運行結果:數據結構

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid18148.hprof ...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Heap dump file created [13355339 bytes in 0.137 secs]
    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 com.thinkinjava.jvm.HeapOOM.main(HeapOOM.java:18)
複製代碼

用MAT工具打開堆快照文件:jvm


「Shallow Heap」:類自己元數據的大小。「Retained Heap」:該類以及它引用的其餘類所佔用空間的總和xss



大概的分析步驟就是這樣,關於MAT的使用,能夠去多瞭解下。工具

棧OOM

  • 線程請求的棧深度大於虛擬機所容許的最大深度,將拋出StackOverflowError。
  • 若是虛擬機在擴展棧時沒法申請到足夠的內存,則拋出OOM。
    測試程序:
/**
 * VM Args:-Xss128k
 * User:wangs
 * Date:2018/8/27
 */
public class StackOOM {

    private int stackLength = 1;

    public static void main(String[] args) {
        StackOOM stackOOM = new StackOOM();
        try {
            stackOOM.test();
        } catch (Throwable e) {
            System.out.println("stack length:" + stackOOM.stackLength);
            throw e;
        }
    }
    private void test() {
        stackLength++;
        test();
    }
}
複製代碼

運行結果:測試

stack length:994
Exception in thread "main" java.lang.StackOverflowError
    at com.thinkinjava.jvm.StackOOM.test(StackOOM.java:25)
    at com.thinkinjava.jvm.StackOOM.test(StackOOM.java:26)
    at com.thinkinjava.jvm.StackOOM.test(StackOOM.java:26)
    at com.thinkinjava.jvm.StackOOM.test(StackOOM.java:26)
    at com.thinkinjava.jvm.StackOOM.test(StackOOM.java:26)
    at com.thinkinjava.jvm.StackOOM.test(StackOOM.java:26)
    at com.thinkinjava.jvm.StackOOM.test(StackOOM.java:26)
    at com.thinkinjava.jvm.StackOOM.test(StackOOM.java:26)
    at com.thinkinjava.jvm.StackOOM.test(StackOOM.java:26)
    at com.thinkinjava.jvm.StackOOM.test(StackOOM.java:26)
    at com.thinkinjava.jvm.StackOOM.test(StackOOM.java:26)
    at com.thinkinjava.jvm.StackOOM.test(StackOOM.java:26)
    at com.thinkinjava.jvm.StackOOM.test(StackOOM.java:26)
    at com.thinkinjava.jvm.StackOOM.test(StackOOM.java:26)
複製代碼

上篇文章深刻理解Java虛擬機之棧幀,介紹了棧幀,咱們來回顧一下:棧幀是保存在虛擬機棧中的,棧幀是用來存儲數據和存儲部分過程結果的數據結構,同時也被用來處理動態連接(Dynamic Linking)、方法返回值和異常分派(Dispatch Exception)。棧幀越大,棧深度越小。方法參數過多或者局部變量過多都會使棧深度變小,若是超過-xss設置的棧內存容量,就會致使棧溢出。ui

方法區OOM

測試程序:spa

/*
 * VM Args: -XX:PermSize=10m -XX:MaxPermSize=10m
 */
public class RuntimeConstantPoolOOM {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        int i = 0;
        while (true) {
            list.add(String.valueOf(i++).intern());
        }
    }
}
複製代碼

在JDK1.6及以前版本中,因爲常量池分配在永久代中(即方法區),咱們能夠經過-XX:PermSize和-XX:MaxPermSize限制方法區大小,從而間接限制其中常量池的容量。運行時常量池溢出:java.lang.OutOfMemoryError: PermGen space線程

JDK1.7常量池不在存儲對象,而是存儲對象的引用。修改虛擬機參數-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError,運行結果以下:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.lang.Integer.toString(Integer.java:401)
    at java.lang.String.valueOf(String.java:3099)
    at com.thinkinjava.jvm.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:14)
複製代碼

異常的意思是程序花在垃圾回收上的時間太多,卻沒有什麼效果。默認的話,若是98%的時間都花在GC上而且回收了纔不到2%的空間的話,虛擬機就會拋這個異常。
JDK1.8移除了永久代,類的元數據信息存儲在元空間。元空間並不在虛擬機中,而是使用本地內存。所以,默認狀況下,元空間的大小僅受本地內存限制,但能夠經過-XX:MetaspaceSize -XX:MaxMetaspaceSize參數來指定元空間的大小。
測試程序:

/**
 * VM Args:-XX:MaxMetaspaceSize=10m
 * User:wangs
 * Date:2018/8/27
 */
public class MetaspaceOOM {
    static ClassPool classPool = ClassPool.getDefault();

    public static void main(String[] args) throws Exception {
        int i = 1;
        while (true){
            classPool.makeClass("com.thinkinjava.jvm" + i++).toClass();
        }
    }
}
複製代碼

運行結果:

Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
    at javassist.ClassPool.toClass(ClassPool.java:1170)
    at javassist.ClassPool.toClass(ClassPool.java:1113)
    at javassist.ClassPool.toClass(ClassPool.java:1071)
    at javassist.CtClass.toClass(CtClass.java:1264)
    at com.thinkinjava.jvm.Metaspace.main(Metaspace.java:16)
複製代碼

直接內存OOM

測試程序:

/**
 * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
 * User:wangs
 * Date:2018/8/27
 */
public class DirectMemoryOOM {

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) throws Exception {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true) {
            unsafe.allocateMemory(_1MB);
        }
    }
}
複製代碼

運行結果:

Exception in thread "main" java.lang.OutOfMemoryError
    at sun.misc.Unsafe.allocateMemory(Native Method)
    at com.thinkinjava.jvm.DirectMemoryOOM.main(DirectMemoryOOM.java:21)複製代碼

若是讀完以爲有收穫的話,歡迎點贊、關注、加公衆號【Java在線】,查閱更多精彩歷史!!!

相關文章
相關標籤/搜索