常見的六種OOM異常和錯誤


1、java.lang.StackOverflowError

報這個錯誤一般是由於方法深層次的調用,默認的線程棧空間大小一般與具體的硬件平台有關。棧內存為線程私有的空間,每個線程都會創建私有的棧內存。棧空間內存設置過大,創建線程數量較多時會出現棧內存溢出StackOverflowError。同時,棧內存也決定方法調用的深度,棧內存過小則會導致方法調用的深度較小,如遞歸調用的次數較少。

Demo:

public class StackOverFlowErrorDemo {

   static int i = 0;

    public static void main(String[] args) {
        stackOverflowErrorTest();
    }

    private static void stackOverflowErrorTest() {

        i++;
        System.out.println("這是第 "+i+" 次調用");
        stackOverflowErrorTest();

    }
}
//運行結果:
。。。。
這是第 6726 次調用
這是第 6727 次調用
Exception in thread "main" java.lang.StackOverflowError
。。。。

注意:這是一個Error!!!!

2、java.lang.OutOfMemoryError: Java heap space

Heap size 設置 JVM堆的設置是指:java程序執行過程中JVM能夠調配使用的內存空間的設置。JVM在啟動的時候會自己主動設置Heap size的值,其初始空間(即-Xms)是物理內存的1/64最大空間(-Xmx)是物理內存的1/4。能夠利用JVM提供的-Xmn -Xms -Xmx等選項可進行設置。Heap size 的大小是Young Generation 和Tenured Generaion 之和。

Demo:

public class OOMHeapSpaceDemo {
    public static void main(String[] args) {
        byte[] bytes = new byte[30*1024*1024];
    }
}

然后修改堆內存的初始容量和最大容量為5MB

運行程序,查看結果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at jvm.OOMHeapSpaceDemo.main(OOMHeapSpaceDemo.java:7)

注意:這是一個Error!!!!

3、java.lang.OutOfMemoryError:GC overhead limit exceeded

GC回收時間過長時會拋出的OutOfMemory。過長是指,超過98%的時間都在用來做GC並且回收了不到2%的堆內存。連續多次的GC,都回收了不到2%的極端情況下才會拋出。假如不拋出GC overhead limit 錯誤會發生什么事情呢?那就是GC清理出來的一點內存很快又會被再次填滿,強迫GC再次執行,這樣造成惡性循環,CPU的使用率一直很高,但是GC沒有任何的進展。

Demo:

/**
 * 調整虛擬機的參數:
 * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
 */
public class GCOverHeadDemo {
    public static void main(String[] args) {
        int i= 0;
        List<String> list = new ArrayList<>();
        while (true){
            list.add(String.valueOf(++i).intern());
            System.out.println(i);

        }
    }
}


//運行結果:
[Full GC (Ergonomics) [PSYoungGen: 1024K->1024K(2048K)] [ParOldGen: 7101K->7099K(7168K)] 8125K->8123K(9216K), [Metaspace: 3264K->3264K(1056768K)], 0.0296282 secs] [Times: user=0.08 sys=0.00, real=0.03 secs] 

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded

可以看到GC前后內存占用時一樣的,也就是說雖然在頻繁的GC,但是並沒有起到什么作用,並沒有回收回來多少的內存。

4、java.lang.OutOfMemoryError:Direct buffer memory

寫NIO程序經常使用到ByteBuffer來讀取或者寫入數據,這是一種基於通道與緩沖區的I/O方式。它可以使用Native函數庫直接分配堆外內存,然后通過一個存儲在java堆里面的DirectByteBuffer對象作為這塊內存的引用進行操作。這樣能在一些場景中提高性能,因為避免了java堆和Native堆中來回復制數據。

  • ByteBuffer.allocate(capability) :這種方式是分配JVM堆內存,屬於GC管轄范圍之內。由於需要拷貝,所以速度相對較慢;
  • ByteBuffer.allocateDirect(capability):這種方式是直接分配OS本地內存,不屬於GC管轄范圍之內,由於不需要內存拷貝所以速度相對較快。

但是如果不斷分配本地內存,堆內存很少使用,那么JVM就不需要執行GC,DirectByteBuffer對象就不會被回收。這時候堆內存充足,但是本地內存已經用光了,再次嘗試分配的時候就會出現OutOfMemoryError,那么程序就直接崩潰了。

Demo:

/**
 * JVM配置參數:
 * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
 */
public class DirectBufferMemoryDemo {
    public static void main(String[] args) {
        System.out.println("配置的MaxDirectMemorySize"+sun.misc.VM.maxDirectMemory()/(double)1024/1024+" MB");
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6*1024*1024);
    }
}


//運行結果:
配置的MaxDirectMemorySize5.0 MB
[GC (System.gc()) [PSYoungGen: 1785K->488K(2560K)] 1785K->728K(9728K), 0.0019042 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 488K->0K(2560K)] [ParOldGen: 240K->640K(7168K)] 728K->640K(9728K), [Metaspace: 3230K->3230K(1056768K)], 0.0077924 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 

Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory

5、java.lang.OutOfMemoryError:unable to create new native thread

准確的說,這一個異常是和程序運行的平台相關的。導致的原因:

  • 創建了太多的線程,一個應用創建多個線程,超過系統承載極限;
  • 服務器不允許應用程序創建這么多的線程,Linux系統默認的允許單個進程可以創建的線程數量是1024個,當創建多 線程數量多於這個數字的時候就會拋出此異常

如何解決呢?

  • 想辦法減少應用程序創建的線程的數量,分析應用是否真的需要創建這么多的線程。如果不是,改變代碼將線程數量降到最低;
  • 對於有的應用,確實需要創建很多的線程,遠超過Linux限制的1024個 限制,那么可以通過修改Linux服務器的配置,擴大Linux的默認限制。

Demo:

public class UnableCreateNewThreadDemo {
    public static void main(String[] args) {
        for (int i = 1; ;i++){
            System.out.println("i = " +i);
            new Thread(()->{
                try {
                    Thread.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },i+"").start();
        }
    }
}

//運行結果:
。。。。
i = 92916
i = 92917
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
。。。。

6、java.lang.OutOfMemoryError:MetaSpace

元空間的本質和永久代類似,都是對JVM規范中的方法區的實現。不過元空間與永久代之間最大的區別在於:元空間不在虛擬機中,而是使用的本地內存。因此,默認情況下,元空間的大小僅僅受到本地內存的限制 。

元空間存放了以下的內容:

  • 虛擬機加載的類信息;
  • 常量池;
  • 靜態變量;
  • 即時編譯后的代碼

模擬MetaSpace空間溢出,我們不斷生成類往元空間里灌,類占據的空間總是會超過MetaSpace指定的空間大小的

查看元空間的大小:java -XX:+PrintFlagsInitial

Demo:

/**
 * JVM參數配置:
 * -XX:MetaSapceSize=5m
 */
public class MetaSpaceDemo {
    public static void main(String[] args) {
        for (int i = 0; i < 100_000_000; i++) {
            generate("eu.plumbr.demo.Generated" + i);
        }
    }
    public static Class generate(String name) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        return pool.makeClass(name).toClass();
    }
}



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM