OutOfMemoryError異常java內存泄漏(Memory Leak)和內存溢出(Memory Overflow)

本篇文章理解源自於《深刻理解java虛擬機》2.4章節 實戰:OutOfMemoryError異常
 
在如下例子中,全部代碼均可以拋出OutOfMemoryError異常,可是要區分究竟是內存泄漏(Memory Leak)仍是內存溢出(Memory Overflow),咱們須要藉助Eclipse Memory Analyzer(也成爲MAT,mat是一個分析Java內存的神器)插件來分析.hprof文件才能得知。
安裝Eclipse Memory Analyzer插件方法:eclipse -> Help -> Eclipse Marketplace -> search框搜索「Memory Analyzer」 -> 點擊install便可安裝。
IBM官方介紹Eclipse Memory Analyzer網址:https://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-ma/index.html?ca=drs-
 
在測試如下例子以前,咱們來了解下VM參數是個什麼東西:
VM arguments參數解析
-verbose:gc 參數表示將在控制檯輸出full GC詳細,例如:[Full GC 168K->97K(1984K), 0.0253873 secs]
-Xms20M 初始堆大小。默認值:物理內存的1/64(<1GB)。默認空餘堆內存小於40%時,JVM就會增大堆直到-Xmx的最大限制。
-Xmx20M 最大堆大小。默認值:物理內存的1/4(<1GB)。默認空餘堆內存大於70%時,JVM會減小堆直到 -Xms的最小限制。
-Xmn10M 年輕代大小。整個堆大小=年輕代大小 + 年老代大小 + 持久代大小。增大年輕代後,將會減少年老代大小.此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8。
-XX:+PrintGCDetails 打印full gc的詳細信息。
-XX:+HeapDumpOnOutOfMemoryError JVM 就會在發生內存泄露時抓拍下當時的內存狀態,也就是咱們想要的堆轉儲文件。也就是會在項目根目錄下生成 *.hprof文件可供分析。
-XX:+HeapDumpOnCtrlBreak 若是你不想等到發生崩潰性的錯誤時纔得到堆轉儲文件,也能夠經過設置以下 JVM 參數來按需獲取堆轉儲文件。
-XX:SurvivorRatio=8 Eden區與Survivor區的大小比值。設置爲8,則兩個Survivor區與一個Eden區的比值爲2:8,一個Survivor區佔整個年輕代的1/10。
VM參數參考該文章:JVM系列三:JVM參數設置、分析( http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html
 
new一個Junit4單元測試類VirtualTest,依次放入如下例子代碼:
例子1
/**
* 以Run AS -> Run Configurations運行。
* 並設置 VM arguments參數以下:
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError
*/
@Test
public void testOutOfMemoryError() {
  List<VirtualTest> list = new ArrayList<VirtualTest>();
  while(true){
    list.add(new VirtualTest());
  }
}
執行代碼後,控制檯拋出異常以下:
[GC (Allocation Failure) [PSYoungGen: 8192K->1018K(9216K)] 8192K->2211K(19456K), 0.0078156 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 9210K->1000K(9216K)] 10403K->7720K(19456K), 0.0356040 secs] [Times: user=0.11 sys=0.00, real=0.04 secs]
…………
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid10604.hprof ...
Heap dump file created [29234562 bytes in 0.214 secs]
Heap
PSYoungGen total 9216K, used 8154K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 99% used [0x00000000ff600000,0x00000000ffdf6a10,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
ParOldGen total 10240K, used 9292K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 90% used [0x00000000fec00000,0x00000000ff513390,0x00000000ff600000)
Metaspace used 4979K, capacity 5182K, committed 5248K, reserved 1056768K
class space used 591K, capacity 626K, committed 640K, reserved 1048576K
異常分析
這個異常咱們要藉助Memory Analyzer工具來查看內存溢出的是哪一個對象,刷新整個項目,點擊根目錄下的java_pid10604.hprof,默認是Overview視圖,上面是一個顯眼的餅圖:
 
在這個餅圖裏面,咱們能夠看到有一個可疑對象在總共16.4MB的內存裏,它佔用了15.5MB的內存,佔用了94.5%的內存。
在餅圖的左下方,這個報告告訴咱們這個內存溢出發生在main這個線程裏面。
接下來咱們固然想知道究竟是哪一個對象乾的,那麼找到餅圖的下方還有一些工具:
點擊Histogram連接,打開Histogram視圖:
咱們發如今Objets這一欄,最多的一個對象實例居然達到810327個,這個對象類是com.virtual.VirtualTest。那麼很清楚了,咱們new出了太多這個類對象,致使垃圾內存溢出。
在dominator_tree視圖中,能夠看到main線程佔用了94.02%的內存
在上圖中,咱們能夠看到該.hprof文件存放的路徑、大小、生成時間、格式、報告中總共產生多少對象,堆大小(16.4MB)等信息。
 
例子2
  /**
     * 以Run AS -> Run Configurations運行。
     * 並設置 VM arguments參數以下:
     * -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
     * 注意:-XX:+HeapDumpOnOutOfMemoryError參數將會使項目根目錄下產生java_*.hprof文件
     */
    @Test
    public void testOutOfMemory2(){
        List<String> list = new ArrayList<String>();
        for(int i=0;i<10000000;i++){
            String str = new String();
            list.add(str);
        }
    }
執行代碼後,控制檯拋出異常以下
java.lang.OutOfMemoryError: GC overhead limit exceeded
Dumping heap to java_pid3360.hprof ...
Heap dump file created [12525143 bytes in 0.136 secs]
異常分析
Sun 官方對此的定義是:「並行/併發回收器在GC回收時間過長時會拋出OutOfMemroyError。過長的定義是,超過98%的時間用來作GC而且回收了不到2%的堆內存。用來避免內存太小形成應用不能正常工做。「
意思就是GC用盡了全力可仍是來不及回收你new出來的那麼多string對象,並且list對象愈來愈大,你還往裏面塞對象,還讓它活着,你還不讓我回收,因而……呵呵……我掛了(>﹏<)
可是我拋出這個異常的目的就是在JVM完全死了以前告訴你我掛了,好讓你來得及實施一些緊急措施,好比緊急保存之類。
參考文章《java.lang.OutOfMemoryError:GC overhead limit exceeded填坑心得》 http://www.cnblogs.com/hucn/p/3572384.html
 
總結
到目前爲止,我還沒能從Eclipse Memory Analyzer工具中發現可以區分出java內存泄漏(Memory Leak)和內存溢出(Memory Overflow)這兩種狀態的直接證據。
我的理解爲:
java內存泄漏(Memory Leak) 當new出不少對象,而GC沒法及時回收這些對象時,會致使內存泄漏。
內存溢出(Memory Overflow) 當一個對象長時間存活,且佔用內存巨大直接威脅到總內存時,會致使內存溢出。
 
如下是其它已在JDK1.8版本中再也不報內存溢出的代碼:
舉例1
  /**
     * 在JDK1.8中不會拋出異常!!!! 
     * 以Run AS -> Run Configurations運行。 並設置 VM arguments參數以下:
     * -Xss128k -XX:+HeapDumpOnOutOfMemoryError
     * 注意:-XX:+HeapDumpOnOutOfMemoryError參數將會使項目根目錄下產生java_*.hprof文件
     */
    @Test
    public void testOutOfMemory_noError() {
        int stackLength = 1;
        try {
            while (true) {
                stackLength = stackLength + 1;
                System.out.println("stack length ==" + stackLength);
            }
        } catch (Throwable t) {
            System.out.println("stack length : " + stackLength);
            throw t;
        }
    }
舉例2
  /**
     * 在JDK1.8中不會拋出異常!!!! 可是會使操做系統假死。
     * 以Run AS -> Run Configurations運行。 並設置 VM arguments參數以下:
     * -Xss2M -XX:+HeapDumpOnOutOfMemoryError
     * 注意:-XX:+HeapDumpOnOutOfMemoryError參數將會使項目根目錄下產生java_*.hprof文件
     */
    @Test
    public void testOutOfMemory_noError2() {
        while (true) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                    }
                }
            });
            t.start();
        }
    }
舉例3
  /**
     * 在JDK1.8中不會拋出異常!!!! 
     * 以Run AS -> Run Configurations運行。 並設置 VM arguments參數以下:
     * -XX:PermSize=10M -XX:MaxPermSize=10M -XX:+HeapDumpOnOutOfMemoryError
     * 注意:-XX:+HeapDumpOnOutOfMemoryError參數將會使項目根目錄下產生java_*.hprof文件
     */
    @Test
    public void testOutOfMemory_noError3() {
        List<String> list = new ArrayList<String>();
        int i=0;
        while (true) {
            list.add(String.valueOf(i));
        }
    }
舉例4
  /** 
     * 在JDK1.8中不會拋出異常!!!! 
     * 以Run AS -> Run Configurations運行。 並設置 VM arguments參數以下: -Xms10m -Xmx10m
     * -XX:PermSize=10M -XX:MaxPermSize=10M -XX:+HeapDumpOnOutOfMemoryError
     * 注意:-XX:+HeapDumpOnOutOfMemoryError參數將會使項目根目錄下產生java_*.hprof文件
     */
    @Test
    public void testOutOfMemory_noError4() {
        while(true){
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(this.getClass());
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                
                @Override
                public Object intercept(Object obj, Method method, Object[] args,
                        MethodProxy proxy) throws Throwable {
                    return proxy.invoke(obj, args);
                }
            });
            enhancer.create();
        }
    }
相關文章
相關標籤/搜索