在java的虛拟機異常中,有兩個異常是大家比較關心的,一個是StackOverflowError,另一個是OutOfMemoryError。今天我們就來看看OutOfMemoryError是怎麼産生的,以及如何去排查這個異常。
概念
要了解什麼是OutOfMemoryError,我們可以直接看一下OutOfMemoryError的源碼,在類上的英文注釋很好的闡述了什麼是OutOfMemoryError,翻譯過來的意思是,由于記憶體不足,虛拟機沒有可配置設定的記憶體了,垃圾回收器也不能釋放更多的記憶體。在生産環境中,由于通路量過大,把記憶體吃滿,會出現OutOfMemoryError的異常,小夥伴們如果沒有經驗的話,往往束手無策,到底是真的記憶體不夠用了,還是自己的程式有問題,也不知道如何去排查這樣的異常。
模拟OutOfMemoryError
在這裡,我們寫一段程式,來模拟一下OutOfMemoryError如何産生,我們建立一個List對象,然後向裡邊不停的添加1M的Byte,如下;
public static void main(String[] args) {
List<Byte[]> list = new ArrayList<>();
int i = 0;
try {
while (true) {
list.add(new Byte[1024 * 1024]);
i++;
}
} catch (Throwable e) {
e.printStackTrace();
System.out.println("執行了"+i+"次");
}
}
- 我們寫了一個while(true)循環,每次都add一個1M的位元組對象,1024*1024正好1M。
- 我們用i的值記錄總共執行了幾次。
- 如果這樣不停的執行下去,不管你有多大的記憶體,都會被吃光的。
我們為了讓程式運作時,快速的抛出OutOfMemoryError異常,可以在java的啟動指令行增加啟動參數,設定堆記憶體的初始值和最大值。這兩個值在生産環境下,通常也是要配置的哦,要充分利用機器的記憶體嘛,如果不配置就會使用預設值。到時候由于記憶體不足向老闆申請機器,可别挨罵哦~
那這兩個參數怎麼去加呢?
- -Xms ,-Xms設定初始堆記憶體的大小
- -Xmx, -Xmx設定最大堆記憶體的大小
通常情況下,這兩個值設定成一樣就可以了,總之,我們設定了堆記憶體的大小。我們在IDEA的啟動配置中,統一設定堆記憶體為80M,如下;

好了~~我們運作一下,看看會不會抛出OutOfMemoryError異常吧
java.lang.OutOfMemoryError: Java heap space
at com.diancan.JavaOOMDemo.main(JavaOOMDemo.java:14)
執行了14次
執行了14次,抛出了OutOfMemoryError異常。但是,如果抛出這樣一個異常,我們怎麼去排查呢?就這一行日志也看不出什麼來啊。
排查
說到排查,如果我們能夠拿到異常時的記憶體快照,然後通過一些工具就可以了進行記憶體的分析了。那麼我們怎麼去拿到記憶體溢出時的快照呢?其實,JDK也為我們提供了這樣的指令參數,我們來看一下吧,
- -XX:+HeapDumpOnOutOfMemoryError,從字面就可以很容易的了解,在發生OutOfMemoryError異常時,進行堆的Dump,這樣就可以擷取異常時的記憶體快照了。
- -XX:HeapDumpPath=D:heap-dump ,這個也很好了解,就是配置HeapDump的路徑,友善我們管理,這裡我們配置為D:heap-dump,當然你也可以根據自己的需要,定義為其他的目錄。
注意,HeapDumpPath的目錄一定要手動建立好,如果沒有這個目錄,Dump會失敗的。
IDEA中的配置,如圖:
我們再運作一下程式,看看是什麼樣子,
java.lang.OutOfMemoryError: Java heap space
Dumping heap to D:\heap-dump\java_pid24312.hprof ...
Heap dump file created [123468648 bytes in 0.141 secs]
java.lang.OutOfMemoryError: Java heap space
at com.diancan.JavaOOMDemo.main(JavaOOMDemo.java:14)
執行了14次
我們發現日志上面多了點東西,建立了一個檔案,在D:heap-dumpjava_pid24312.hprof。這個檔案就是我們的記憶體快照。那麼問題來了,我們如何檢視這個檔案呢?直接打開是不行的,用寫字闆等也是不行的,那怎麼辦?其實也沒那麼複雜,使用JDK自帶的jvisualvm就可以檢視。
這裡邊有個小坑,如果大家用JDK8,可以在JDK的bin目錄下找到
jvisualvm.exe
,但是如果你使用的是JDK8以上的版本,就本示例中,使用的是JDK11,在bin目錄下是找不到
jvisualvm.exe
的。大家可以去
visualvm的首頁下載下傳。
我們啟動visualvm,進入到如下的頁面,
然後,點選左上角的加載快照按鈕,然後選擇剛才我們Dump的檔案,
我們重點看一下右側中間的部分,
類的執行個體大小排序,可以看到,我們的Byte占了96.5%。詳細的資訊,我們可以點進去看,包括變量裡存的内容,這樣我們就可以很快的定位到記憶體溢出的位置,并且可以判斷是真的記憶體不夠了,還是我們的代碼出了問題。