OutOfMomeryError異常實例與處理

在java虛擬機中處理程序計數器,其他的比如堆,方法區,java虛擬機棧,本地方法棧都會出現OOM(OutOfMomery)異常。下面通過拜讀《深入理解java虛擬機》以及實際操作如下:

堆溢出

堆溢出的條件是不斷的創建對象,並且創建的對象無法被GC自動回收(怎樣保證不被GC回收, 確保GC Roots 與對象之間存在可達的路徑,即創建的對象都在使用)

實例:

import java.util.ArrayList;
import java.util.List;

public class HeapOOM {

	static class OOMObject{}
	
	public static void main(String[] args){
		List<OOMObject> list=new ArrayList<>();
		while(true){
			list.add(new OOMObject());
		}
	} 
}

運行結果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3204)
	at java.util.Arrays.copyOf(Arrays.java:3175)
	at java.util.ArrayList.grow(ArrayList.java:246)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:220)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:212)
	at java.util.ArrayList.add(ArrayList.java:443)
	at HeapOOM.main(HeapOOM.java:11)

我在測試的時候運行了兩分鐘纔出現堆異常,因爲我沒有設置原始堆的大小,是默認堆大小進行測試的,導致電腦嘩嘩響哈哈。

處理方法:可以通過Eclipse Momery-Analyze工具對堆轉儲快照進行分析。確認內存中的對象是內存泄漏(Momery Leak)還是內存溢出(Momery Overflow)。

至於快照怎麼生成,和Eclipse Momery-Analyze工具怎麼使用,這裏也簡單的記錄下
快照生成的方式有很多,我用了兩種方法生成。

  1. 在運行的文件或者項目,點擊Run As —>Run Configurations…進入圖2的界面,在Agruments模塊的VM Agruments中添加:-XX:+HeapDumpOnOutOfMemoryError
    然後點擊運行,就會在項目的根目錄下生成快照文件

在這裏插入圖片描述

在這裏插入圖片描述
生成如下圖所示的快照,通過Eclipse Momery-Analyze工具直接打開分析。
在這裏插入圖片描述
2. 在控制檯通過Jmap 生成快照。
先讓項目或程序跑起來,通過jps命令查看項目對應的PID,然後通過jmap命令生成快照。
在這裏插入圖片描述
生成虧快照的jmap命令:jamp -dump:format=b,file=文件名 PID
生成的快照存放的位置,在下面有提示。

好了,現在有快照了,接下來就用Eclipse Momery-Analyze工具來分析吧。用分析工具打開之後會出現這些東西,點擊datail 可以查看對象佔用內存情況。看看是內存泄露還是內存溢出。
在這裏插入圖片描述
在這裏插入圖片描述

如果是內存泄露,通過上述方法定位到問題代碼,進行代碼優化,防止內存泄露,如果是內存溢出,則需要調大虛擬機的參數(-Xmx和-Xms)。

虛擬機棧和本地方法棧溢出

棧溢出的條件:一般是遞歸調用自身,導致一直入棧而沒有進行相應的出棧操作,最終導致棧溢出。在HotSpotVM 中沒有區分虛擬機棧和本地方法棧,所以將二者就暫時統稱爲棧溢出。

實例:


public class StackOOM {

	private int stackLength=1;
	
	public void stackLeak(){
		stackLength++;
		stackLeak();
	}
	public static void main(String[] args){
		StackOOM obj=new StackOOM();
		try {
			obj.stackLeak();
		} catch (Exception e) {
			System.out.println("Length:"+obj.stackLength);
			throw e;
		}
	}
}

結果:

Exception in thread "main" java.lang.StackOverflowError
	at StackOOM.stackLeak(StackOOM.java:8)
	at StackOOM.stackLeak(StackOOM.java:8)
	at StackOOM.stackLeak(StackOOM.java:8)
	at StackOOM.stackLeak(StackOOM.java:8)
	at StackOOM.stackLeak(StackOOM.java:8)
	at StackOOM.stackLeak(StackOOM.java:8)
	at StackOOM.stackLeak(StackOOM.java:8)
	at StackOOM.stackLeak(StackOOM.java:8)
	......

像這種棧溢出的問題,通過擴大棧的內存是沒有太大的意義的,需要對代碼進行優化處理。

方法區和本地運行時常量池溢出

我們可以通過-XX:PermSize和-XX:MaxPermSize來設置方法區的大小

實例jdk1.6上可以測試,1.7測試不了想要的結果

import java.util.ArrayList;
import java.util.List;

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());
		}
		
	}

}

然後書中寫的那個測試用例,沒有看明白需要導入其他的jar包就沒有進行測試了。下次有機會再來研究。

總結:主要都是堆棧的溢出。堆溢出是不斷的創建對象,且保證創建的對象不被GC,棧溢出是不斷的壓棧而不出棧,遞歸調用且不停止會導致棧溢出。


參考文獻:https://blog.csdn.net/albertfly/article/details/51446943
https://blog.csdn.net/haolyj98/article/details/78361867
《深入理解Java虛擬機》第二版 周志明

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章