使用了標誌-XX:+HeapDumpOnOutOfMemoryError,JVM會在遇到OutOfMemoryError時拍攝一個「堆轉儲快照」,並將其保存在一個文件中。java
對以下一段代碼,【代碼1】數組
Java代碼 測試
設置虛擬機參數爲:-Xmx40m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=E:\Java\dump優化
執行程序,很快會拋出異常:spa
java.lang.OutOfMemoryError: Java heap space線程
Dumping heap to E:\Java\dump\java_pid10400.hprof ...blog
Heap dump file created [1192880 bytes in 0.024 secs]內存
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceget
奇怪的是Heap dump file只有一兆多一點,用JProfiler打開這個文件,並無看到致使內存溢出的long[];剛開始覺得是堆轉儲時,JVM會忽略掉只被線程棧引用的數組,進一步測試,發現並非這個緣由;查看相應的class文件,反編譯後獲得:編譯器
【將代碼1經過JDK1.6編譯後的字節碼反編譯後獲得的代碼】
Java代碼
其中long[] arr 的定義從循環外面變到了循環裏面,應該是編譯器進行了優化,這樣修改後,功能並無變化,但long[] arr的生存範圍變小了,生存範圍是從聲明到本次循環結束;每次循環開始時,在線程棧中聲明一個指向long[]的引用 arr,而後在堆中建立一個指定大小的long[],把它的引用賦給arr;在每次循環結束,進入下次循環前,線程棧中的引用arr就會被銷燬,它所指向的long[]就變成了沒有被引用的實例;進入了下次循環,又從新在線程棧中聲明一個引用arr,在堆中建立一個指定大小的long[],把它的引用賦給arr。
分析:虛擬機參數配置了-Xmx40m,在堆內存的使用量超過40M時,虛擬機就會拋出OutOfMemoryError: Java heap space,同時將堆內存轉儲到文件中,這時候前面循環建立的long[]實例沒有被引用,應該已經被垃圾回收,因此Heap dump file中沒有程序建立的long[]實例。
在源代碼arr = new long[i];的後面加上顯示堆內存使用的語句:
Java代碼
能夠看出每次執行arr = new long[i];後堆內存的使用狀況;配置-Xmx40m的狀況下,拋出異常前最後一次正常執行的循環的輸出信息爲:
size : 4194304
maxMemory : 39.75M
totalMemory : 38.50M
freeMemory : 6.27M
將代碼1改成:【代碼2】
Java代碼
查看相應的class文件,反編譯後獲得:
【將代碼2經過JDK1.6編譯後的字節碼反編譯後獲得的代碼】
Java代碼
其中long[] arr 的定義跟源代碼一致,是在循環外面,應該是由於有初始化的代碼,沒辦法再把定義移到循環裏面;這種狀況下,arr的生存範圍是從聲明到程序結束;每次循環開始時,會在堆中建立一個指定大小的long[],把它的引用賦給arr,這樣arr以前指向的long[]就變成沒有被引用的實例;進入了下次循環,在堆中建立一個指定大小的long[],把它的引用賦給arr,在賦值完成前,arr指向上次循環建立的long[]實例,賦值完成後,arr就指向本次循環建立的long[]實例,這時候上次循環建立的long[]實例就變成沒有被引用的實例。
設置虛擬機參數爲:-Xmx40m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=E:\Java\dump
執行程序,很快會拋出異常:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to E:\Java\dump\java_pid11020.hprof ...
Heap dump file created [17970188 bytes in 0.172 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Heap dump file有十多兆,用JProfiler打開這個文件,能夠找到一個佔用16M內存的long[]。
分析:在程序由於沒有足夠的堆內存建立實例而拋出OutOfMemoryError時,引用arr仍然指向上次循環建立的long[]實例,在JVM將堆內存轉儲到文件中時,會把這個long[]實例也考慮進去;這個long[]實例被arr引用,arr位於線程棧中,因此上圖中顯示long[]實例被java stack引用。
在源代碼arr = new long[i];的後面加上顯示堆內存使用的語句,
能夠看到拋出異常前最後一次正常執行的循環的輸出信息爲:
size : 2097152
maxMemory : 39.75M
totalMemory : 33.26M
freeMemory : 8.92M
最後一次正常建立的long[]的size爲2097152,佔用了16M內存,而代碼1執行時最後一次正常建立的long[]的size爲4194304,佔用了32M內存。
分析:代碼1在循環中建立long[]實例時,上次循環建立的long[]實例沒有被引用,能夠被垃圾回收掉,因此在參數Xmx40m下,代碼1建立佔用32M內存的long[]仍是能夠正常執行的,試圖建立佔用64M內存的long[]才拋出異常;代碼2在循環中建立long[]實例時,上次循環建立的long[]實例還在被arr引用,不能被垃圾回收掉,代碼2在建立佔用16M內存的long[]實例時,前一個循環建立的佔8M內存的long[]實例還不能被回收,8+16=24 < 40,因此此次可以正常執行,下一個循環要嘗試建立佔32M內存的long[]實例,這時候佔16M內存的long[]實例還不能被回收,16+32=48>40,堆內存不夠用,只好拋出異常。
小結:經過分析OutOfMemoryError時生成的堆轉儲文件,有助於找到內存不夠用的緣由;若是生成的堆轉儲文件的大小跟最大堆內存的配置有很大差別,就要分析拋出異常的代碼,查找緣由,還能夠將字節碼反編譯,看看編譯器是否是對代碼的結構進行了調整。