JVM內存溢出異常OOM

棧溢出 StackOverflowError

Java 裏的 StackOverflowError。拋出這個錯誤表明應用程序因爲深遞歸導致棧被耗盡了。每當java程序啓動一個新的線程時,java虛擬機會爲他分配一個棧,java棧以幀爲單位保持線程運行狀態;當線程調用一個方法是,jvm壓入一個新的棧幀到這個線程的棧中,只要這個方法還沒返回,這個棧幀就存在。 如果方法的嵌套調用層次太多(如遞歸調用),隨着java棧中的幀的增多,最終導致這個線程的棧中的所有棧幀的大小的總和大於-Xss設置的值,而產生生StackOverflowError溢出異常。
StackOverflowError 是 VirtualMachineError 的擴展類,VirtualMachineError 表明 JVM 中斷或者已經耗盡資源,無法運行。而且,VirtualMachineError類擴展自 Error 類,這個類用於指出那些應用程序不需捕獲的嚴重問題。因爲這些錯誤是在可能永遠不會發生的異常情況下產生,所以方法中沒有在它的 throw 語句中聲明。
最常見的可能耗光 Java 應用程序的棧的場景是程序裏的遞歸。遞歸時一個方法在執行過程中會調用自己。 遞歸被認爲是一個強大的多用途編程技術,爲了避免出現 StackOverflowError,使用時必須特別小心。

public class StackErrorMock {

    private static int index = 1;
     
    public void call(){
        index++;
        call();
    }
 
    public static void main(String[] args) {
        StackErrorMock mock = new StackErrorMock();
        try {
            mock.call();
        }catch (Throwable e){
            System.out.println("Stack deep : "+index);
            e.printStackTrace();
        }
    }
}

catch 捕獲的是 Throwable,而不是 Exception。因爲 StackOverflowError 和 OutOfMemoryError 都不屬於 Exception 的子類。

在-Xss100M時
Stack deep : 6507194
java.lang.StackOverflowError
at StackErrorMock.call(StackErrorMock.java:8)

在-Xss1M時
Stack deep : 21967
java.lang.StackOverflowError
at StackErrorMock.call(StackErrorMock.java:8)

不配置時
Stack deep : 22106
java.lang.StackOverflowError
at StackErrorMock.call(StackErrorMock.java:8)

依賴於安裝的 Java 虛擬機,默認的線程棧大小可能是 512KB 或者 1MB。你可以使用 -Xss 標識來增加線程棧的大小。這個標識即可以通過項目的配置也可以通過命令行來指定。-Xss 參數的格式:

-Xss<size>[g|G|m|M|k|K]

堆溢出 OutOfMemoryError

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

public class HeapOverflow {
    public static void main(String[] args) {
        List<Object> listObj = new ArrayList<Object>();
        for(int i=0; i<10; i++){
            Byte[] bytes = new Byte[1024*1024];
            listObj.add(bytes);
        }
        System.out.println("已添加");
    }
}

設置參數

-Xms1m -Xmx5m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError

根據以下的信息,可以看到,年輕代使用1536k,老年代4096(對象空間object space 4096K),其他Metaspace 2672K。

[GC (Allocation Failure) [PSYoungGen: 510K->504K(1024K)] 510K->520K(1536K), 0.0190917 secs] [Times: user=0.00 sys=0.00, real=0.02 secs] 
[GC (Allocation Failure) [PSYoungGen: 752K->488K(1536K)] 768K->584K(5632K), 0.0009955 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 488K->504K(1536K)] 584K->608K(5632K), 0.0007268 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 504K->0K(1536K)] [ParOldGen: 104K->534K(2048K)] 608K->534K(3584K), [Metaspace: 2640K->2640K(1056768K)], 0.0153461 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 534K->534K(5632K), 0.0003560 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 534K->522K(3072K)] 534K->522K(4608K), [Metaspace: 2640K->2640K(1056768K)], 0.0065765 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid8172.hprof ...
Heap dump file created [1232301 bytes in 0.008 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at HeadOverflow.main(HeadOverflow.java:8)
Heap
PSYoungGen total 1536K, used 41K [0x00000000ffe00000, 0x0000000100000000, 0x0000000100000000)
eden space 1024K, 4% used [0x00000000ffe00000,0x00000000ffe0a450,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 4096K, used 522K [0x00000000ffa00000, 0x00000000ffe00000, 0x00000000ffe00000)
object space 4096K, 12% used [0x00000000ffa00000,0x00000000ffa828b8,0x00000000ffe00000)
Metaspace used 2672K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 291K, capacity 386K, committed 512K, reserved 1048576K

常量池永久代溢出(java8以前)

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

public class RuntimeConstantPoolOOM {
    /*
     * VM Args 1.8以前: -XX:PermSize=1M -XX:MaxPermsize=2M
     * 1.8以後:-XX:MetaspaceSize=1M -XX:MaxMetaspaceSize=2M -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
     */
 
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        
        //使用list保持常量池的引用,避免被Full GC回收
        List<String> list=new ArrayList<String>();
        int i=0;
        while(true)
        {
            list.add(String.valueOf(i).intern());
        }
 
    }

}

在 JDK 1.8 中, HotSpot 已經沒有 “PermGen space”這個區間了,取而代之是一個叫做 Metaspace(元空間) 的東西。
可以通過以下參數來指定元空間的大小:

1.8以前: -XX:PermSize=1M -XX:MaxPermsize=2M
1.8以後:-XX:MetaspaceSize=1M -XX:MaxMetaspaceSize=2M -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError

  -XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize時,適當提高該值。
  -XX:MaxMetaspaceSize,最大空間,默認是沒有限制的。

  除了上面兩個指定大小的選項以外,還有兩個與 GC 相關的屬性:
  -XX:MinMetaspaceFreeRatio,在GC之後,最小的Metaspace剩餘空間容量的百分比,減少爲分配空間所導致的垃圾收集
  -XX:MaxMetaspaceFreeRatio,在GC之後,最大的Metaspace剩餘空間容量的百分比,減少爲釋放空間所導致的垃圾收集

java.lang.OutOfMemoryError: Metaspace
Dumping heap to java_pid7928.hprof ...
Error occurred during initialization of VM
GC triggered before VM initialization completed. Try increasing NewSize, current value 84M.

直接內存溢出

直接內存並不是虛擬機運行時數據區的一部分,也不是Java 虛擬機規範中定義的內存區域。在JDK1.4 中新加入了NIO(New Input/Output)類,引入了一種基於通道(Channel)與緩衝區(Buffer)的 I/O 方式,它可以使用 native 函數庫直接分配堆外內存,然後通過一個存儲在Java堆中的 DirectByteBuffer 對象作爲這塊內存的引用進行操作。這樣能在一些場景中顯著提高性能,因爲避免了在 Java 堆和 Native 堆中來回複製數據。

本機直接內存的分配不會受到Java 堆大小的限制,受到本機總內存大小限制
直接內存由 -XX:MaxDirectMemorySize 指定
直接內存申請空間耗費更高的性能
直接內存IO讀寫的性能要優於普通的堆內存
當我們的需要頻繁訪問大的內存而不是申請和釋放空間時,通過使用直接內存可以提高性能。

由於申請直接內存不由虛擬機管理,所以由此導致的 OOM 是不會在 Heap Dump 文件中看出明顯的異常。當 OOM 後發現 Dump 文件很小同時程序直接或間接使用了 NIO ,就可以考慮一下這方面的原因。

 

import java.lang.reflect.Field;
import sun.misc.Unsafe;

public class DirectrMemoryOOM {
    private static final int _1M = 1024 * 1024;
    
    public static void main(String[] args) throws Exception {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        int index =0 ;
        @SuppressWarnings("restriction")
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        try {
            while(true) {
                unsafe.allocateMemory(_1M);
                index++;
            }
        }catch (Throwable e){
            System.out.println("deep : "+index);
            e.printStackTrace();
        }
        

    }

}

參數

-Xmx20M -XX:MaxDirectMemorySize=10M

deep : 51966java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at DirectrMemoryOOM.main(DirectrMemoryOOM.java:16)

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