在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工具怎麼使用,這裏也簡單的記錄下
快照生成的方式有很多,我用了兩種方法生成。
- 在運行的文件或者項目,點擊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虛擬機》第二版 周志明