幾種常見的OOM異常
oom異常就是Out Of Memory Error 內存溢出異常,是我們開發中常見的異常。oom異常也分成多種。
java.lang.OutOfMemoryError: Java heap space 堆空間溢出,最常見的
在創建大對象的時候特別注意堆內存的使用,避免產生堆的內存溢出
模擬一下
/**
* 堆內存溢出,這是工作中最常見的OOM故障
* 1:在JVM啟動參數的時候將堆內存設置成10M -Xmx10m -Xms10m
*/
public class HeapSpaceOomDemo {
public static void main(String[] args) {
//創建一個20M的對象
Byte[] b = new Byte[20*1024*1024];
}
}
控制台打印的結果:
stackoverflowError 棧空間溢出
模擬:
/**
* OMM 之 java.lang. StackOverflowError 棧空間溢出,棧管運行,每個方法就是一個棧幀,循環調用方法,會出現這種問題
*/
public class StackOverFlowDemo {
public static void main(String[] args) {
stackoverflowError();
}
private static void stackoverflowError() {
stackoverflowError();
}
}
控制台結果:
GC overhead limit exceeded GC時間太長引發的異常
GC回收時間過長時會拋出outOfMemroyError,過長的定義是:超過98%的時間用來做GC並且回收不到2%的堆內存。
連續多次GC都只回收了不到2%的極端情況下會拋出。假如不拋出GC overhead limit 錯誤會發生什么信況呢?
那就是GC清理的這么點內存很快會再次填滿,迫使GC 再次執行。這樣就形成惡性循環,
CPU使用率一直是100%, 兩GC 卻沒有任何效果
模擬:
/**
* GC overhead limit exceeded
* JVM參數設置:8U
* -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
*/
public class OverheadLimitExceededDemo {
public static void main(String[] args) {
int i =0;
final ArrayList list = new ArrayList();
while (true){
list.add(String.valueOf(++i).intern());
}
}
}
結果:
Directbuffer memory buffer 內存溢出,在
導致原因:
寫NIO程序經常使ByteBuffer讀取或者寫入數據,這是一種基於通道(Channel)與緩沖區(Buffer)的I/0方式,
它可以使用Native函數庫直接分配堆外內存,然后通過一個存儲在Java堆里面的DirectByteBuffer對象作為這塊內存的引用進行操作。
這樣能在一些場景中顯著提高性能, 因為避免了在Java堆和Native堆中來回復制數據。
ByteBuffer. allocate(capability) 第一種方式是分配JVM堆內存, 屬於GC 營結范圍,由於需要拷貝所以速度相對較慢
ByteBuffer. allocteDirect(capability)第2種方式是分配操作系統本地內存,不屬於GC 管轄范圍,由於不需要內存拷貝所以速度相對較快。
但如果不斷分配本地內存,堆內存很少使用,那么JVM就不需要執行CG, DirectByteBuffer對象們就不會被回收,
這時候堆內存充足,但本地內存可能已經使用光了,再次嘗試分配本地內存就會出OutOfMemoryError,程序就直接崩潰。
模擬:
/**
* java.lang.OutOfMemoryError: Direct buffer memory 演示
* JVM參數配置:-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
*/
public class DirectBufferMemoryDemo {
public static void main(String[] args) {
System.out.println("配置的maxDirectMemory: " + (sun.misc.VM.maxDirectMemory() / (double) 1024 / 1024) + "MB");
//最大5M 申請6M
ByteBuffer bb = ByteBuffer . allocateDirect(6* 1024*1024);
}
}
結果:
unable to create new native thread 無法創建線程
高並發請求服務器時,經常出現如下異常:java.Lang.OutOfMemoryError: unable to create new native thread准確的講native thread 異常與對應的平台有關
導致原因:
你的應用創建了太多線程了,一個應用進程創建多個線程,超過系統承裁極限。
你的服務器並不允許你的應用程序創建這么多線程linux系統默認允許單個進程可以創建的線程數是1024個,你的應用創建超過這個數量,就會報java. lang. OutOfMemoryError: unable to create new native thread
解決辦法:
1.想辦法降低你應用程序創建線程的數量,分析應用是否真的需要創建這么多線程如果不是,改代碼將線程數量降到最低
2.對於有的應用,確實需要創建很多線程,遠超過Linux系統的默認1024個線程的限制,可以通過修改Linux服務器配置,擴大linux默認限制
模擬:這個類拷貝到linux下執行
/**
* java.Lang.OutOfMemoryError: unable to create new native thread
*/
public class UnableCreateNewThreadDemo {
public static void main(String[] args) {
for (int i = 1;; i++) {
System.out.println("i = " + i);
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
},""+i).start();
}
}
}
java.lang.OutOfMemoryError: Metaspace 元空間溢出
Java 8及之后的版本使用Metaspace來替代永久化。Metaspace.是方法區在Hotspot中的實現,它與持久代最大的區別在於: Metaspace 不在虛擬機內存中而是使用本地內存
也即在java8中classe metadata(the virtual machines internal presentation of Java class), 被存儲在叫做Metaspace的native memory
永久代(java8后被原空間Metaspace取代了)存放了以下信息:
虛擬機加載的類信息
常量池
靜態變量
即時編譯后的代碼
模擬Metaspace空間溢出,我們不斷生成類往元空間灌,類占據的空間總是會超過Metaspace指定的空間大小的
/**
* 異常演示: java.lang.OutOfMemoryError: Metaspace
* -XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m
*/
class OomTest{}
public class MetaspaceDemo {
public static void main(String[] args) {
int i = 0;
try {
while (true){
i++;
final Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OomTest.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invoke(o,args);
}
});
enhancer.create();
}
}catch (Exception e){
System.out.println("*****多少次發生異常 " + i);
e.printStackTrace();
}
}
}
執行后的截圖: