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