[JVM]OOM實(shí)例分析

本文內(nèi)容總結(jié)自《深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐(第2版)》中的第2章。

  • 通過設(shè)置虛擬機(jī)的啟動(dòng)參數(shù)來模擬內(nèi)存區(qū)域溢出的情況
  • 通過異常信息判斷出是哪個(gè)內(nèi)存溢出了,知道什么樣的代碼可能會(huì)導(dǎo)致內(nèi)存溢出
Java內(nèi)存區(qū)域
  • 設(shè)置虛擬機(jī)的啟動(dòng)參數(shù)

    在使用代碼進(jìn)行驗(yàn)證之前,我們需要先了解如何設(shè)置虛擬機(jī)的啟動(dòng)參數(shù),設(shè)置方法有兩種。

  • 使用命令行執(zhí)行程序時(shí)
    使用命令行運(yùn)行程序時(shí),直接在java后面跟上參數(shù)和程序名稱

D:\>javac HeapOOM.java

D:\>java HeapOOM

D:\>java -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError HeapOOM
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid9512.hprof ...
Heap dump file created [27898328 bytes in 0.121 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOf(Arrays.java:2245)
        at java.util.Arrays.copyOf(Arrays.java:2219)
        at java.util.ArrayList.grow(ArrayList.java:242)
        at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)
        at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)
        at java.util.ArrayList.add(ArrayList.java:440)
        at HeapOOM.main(HeapOOM.java:16)
  • 使用IDE執(zhí)行程序時(shí)
    直接在VM Arguments中設(shè)置
使用IDE執(zhí)行程序
  • Java堆溢出

  • VM arguments
    設(shè)置Java堆的大小20MB,將堆的最小值-Xms參數(shù)和最大值-Xmx參數(shù)設(shè)置為一樣,避免堆自動(dòng)擴(kuò)展。通過設(shè)置參數(shù)-XX:+HeapDumpOnOutOfMemoryError,在發(fā)生內(nèi)存異常時(shí)可以生成堆轉(zhuǎn)存儲(chǔ)的dump文件,通過Eclipse Memory Analyzer (MAT)工具可以分析該文件。
 VM Args:
 -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError  
Option Description
-Xms20M -Xmx20M to specify that a maximum heap size of 20 Mbytes
-XX:+HeapDumpOnOutOfMemoryError Dump heap to file when java.lang.OutOfMemoryError is thrown
  • Code
    根據(jù)Java內(nèi)存區(qū)域可知,通過new指令創(chuàng)建的對(duì)象是放到Java堆中的。下面這段代碼的功能就是一直在創(chuàng)建OOMObject對(duì)象,當(dāng)OOMObject對(duì)象占用大小超出設(shè)置的20M時(shí),就會(huì)報(bào)出OutOfMemoryError異常,并提示發(fā)生異常的區(qū)域?yàn)?strong>"Java heap space"
import java.util.ArrayList;
import java.util.List;

/**
 * VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError  
 */
public class HeapOOM {
    static class OOMObject {

    }

    public static void main(String[] agrs) {
        List<OOMObject> list = new ArrayList<OOMObject>();

        while (true) {
            list.add(new OOMObject());
        }
    }
}
  • Output
    發(fā)生異常后生產(chǎn)的dump文件的默認(rèn)名稱為"java_pid進(jìn)程id.hprof",默認(rèn)生成路徑為工程的根目錄
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid9512.hprof ...
Heap dump file created [15782234 bytes in 0.074 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2760)
    at java.util.Arrays.copyOf(Arrays.java:2734)
    at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
    at java.util.ArrayList.add(ArrayList.java:351)
    at HeapOOM.main(HeapOOM.java:16)

使用MAT打開 java_pid9512.hprof,可以生成一個(gè)分析報(bào)告

使用MAT打開 java_pid9512.hprof

點(diǎn)擊進(jìn)入Dominator Tree界面,可以將對(duì)象占用Heap大小進(jìn)行排序,由下圖可以看出main thread里面生成了很多HeapOOM$OOMObject對(duì)象,導(dǎo)致暫用了97.49%的內(nèi)存。

Dominator Tree
  • 虛擬機(jī)棧溢出

  • VM arguments
    使用-Xss參數(shù)減少棧內(nèi)存的容量
 VM Args:
 -Xss128k
  • Code
    stackLeak()方法不斷地自己調(diào)用自己,導(dǎo)致棧幀的深度一直在增加,當(dāng)超出棧內(nèi)存容量時(shí),將會(huì)拋出java.lang.StackOverflowError異常
/**
 * VM Args:-Xss128k
 */
public class JavaVMStackSOF {
    private int stackLength = 1;

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

    /**
     * @param args
     * @throws Throwable
     */
    public static void main(String[] args) throws Throwable {
        // TODO Auto-generated method stub
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        } catch (Throwable e) {
            // TODO: handle exception
            System.out.println("stack length : " + oom.stackLength);
            throw e;
        }
    }

}
  • Output
stack length : 11432
Exception in thread "main" java.lang.StackOverflowError
    at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:8)
    at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:9)
    at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:9)
    ... ...
  • 方法區(qū)和運(yùn)行時(shí)常量池異常

運(yùn)行時(shí)常量池是方法區(qū)的一部分,而方法區(qū)屬于"永久代"(PermGen space)

  • VM arguments
    通過-XX:PermSize和 -XX:MaxPermSize限制永久代的大小
  VM Args:
  -XX:PermSize=10M -XX:MaxPermSize=10M
  • Code
    String.intern()的作用:如果字符串常量池中已經(jīng)包含一個(gè)等于此String對(duì)象的字符串,則返回常量池中這個(gè)字符串的String對(duì)象;否則,將此String對(duì)象包含的字符串添加到常量池中,并且返回此String對(duì)象的引用。
    下面這段代碼一直在往常量池中添加字符串,直到超出常量池的容量。
import java.util.ArrayList;
import java.util.List;

/**
 * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
 */
public class RuntimeConstantPoolOOM {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        List<String> list = new ArrayList<String>();
        int i = 0;
        while (true) {
            list.add(String.valueOf(i++).intern());
        }
    }

}
  • Output
  • JDK 1.6及之前的版本中,常量池分配在永久代內(nèi),可以通過-XX:PermSize和 -XX:MaxPermSize限制永久代的大小,當(dāng)常量池超出容量時(shí),會(huì)拋出異常"Exception in thread "main" java.lang.OutOfMemoryError: PermGen space"

  • 由于JDK 1.7的HotSpot中,已經(jīng)把原本放在永久代中的字符串常量池移除,因此使用JDK 1.7運(yùn)行這段代碼時(shí)不會(huì)拋出異常,while循環(huán)將一直進(jìn)行下去

JDK 1.6和JDK 1.7運(yùn)行結(jié)果不同
  • 本機(jī)直接內(nèi)存溢出

  • VM arguments
    DirectMemory容量可以通過MaxDirectMemorySize指定,如果不指定,則默認(rèn)與Java堆最大值一樣(-Xmx指定)
 VM Args: 
-Xmx20M -XX:MaxDirectMemorySize=10M
  • Code
    通過Unsafe對(duì)象的allocateMemory方法申請(qǐng)直接內(nèi)存,當(dāng)超出DirectMemory容量時(shí),拋出異常java.lang.OutOfMemoryError
import java.lang.reflect.Field;
import sun.misc.Unsafe;

/*
 * VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M
 */
public class DirectMemoryOOM {
    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) throws IllegalArgumentException,
        IllegalAccessException {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true) {
            unsafe.allocateMemory(_1MB);
        }
    }
}
  • Output
Exception in thread "main" java.lang.OutOfMemoryError
        at sun.misc.Unsafe.allocateMemory(Native Method)
        at DirectMemoryOOM.main(DirectMemoryOOM.java:16)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理,因此不免有一些不準(zhǔn)確的地方,同時(shí)不同JDK版本的...
    高廣超閱讀 16,062評(píng)論 3 83
  • JVM內(nèi)存模型Java虛擬機(jī)(Java Virtual Machine=JVM)的內(nèi)存空間分為五個(gè)部分,分別是: ...
    光劍書架上的書閱讀 2,783評(píng)論 2 26
  • 一.操作系統(tǒng)相關(guān)基礎(chǔ)知識(shí) 1.物理內(nèi)存、虛擬內(nèi)存、邏輯地址與交換空間 物理內(nèi)存(RAM):加載到內(nèi)存地址寄存器中的...
    Geeks_Liu閱讀 9,507評(píng)論 6 31
  • 題目1:輪播的實(shí)現(xiàn)原理是怎樣的?如果讓你來實(shí)現(xiàn),你會(huì)抽象出哪些函數(shù)(or接口)供使用?(比如 play()) 布局...
    阿魯提爾閱讀 597評(píng)論 0 2
  • 手殘黨。。。
    冷酷的睡睡閱讀 352評(píng)論 0 0

友情鏈接更多精彩內(nèi)容