JVM 中的異常
StackOverflowError
在 JVM 的棧中,如果執行緒要建立的棧幀大小大於棧容量的大小時,就會丟擲 java.lang.StackOverflowError。比如下面的程式碼
public class StackErrorTest { public static void main(String[] args) { main(args); } }
無限遞迴,那麼就會不停的建立棧幀,最終撐爆棧空間,丟擲棧移除異常。
OOM:Java heap space
堆記憶體溢位,當堆空間不足以存放建立的物件時就會發生堆異常。具體模擬方式可以參見下面程式碼:
public class OverHeadOOM { public static void main(String[] args){ int i = 0; List<String> list = new ArrayList<>(); try { while (true){ list.add(String.valueOf(++i)); } } catch (Exception e) { System.out.println(i); e.printStackTrace(); } } }
為了讓結果更快地展示出來,可以把堆空間大小調小一些:-Xms8m -Xmx8m。
OOM:GC overhead limit exceeded
這的發生的原因和上面 Java heap space 差不多,上面是堆空間不足,這個是還未達到堆空間不足,但是超過 98% 的時間用來做 GC 並且回收了不到 2% 的堆記憶體,這時就會立刻觸發當前的異常。
如果以上面的例子來看,如果將堆空間引數設定為 -Xms10m -Xmx10m。就會發生當前異常。
OOM:Direct buffer memory
直接記憶體溢位。
直接記憶體是 JVM 向系統申請的記憶體,由於其是系統記憶體,所以在 io 時沒有狀態切換和不必要的資料拷貝,所以相比於非直接記憶體的 io 執行效率會更高。JDK8 中方法區的實現元空間也是屬於直接記憶體。
在使用 nio 進行緩衝區的定義時,一般是 Buffer.allocate() 來定義的,這種方式是在 JVM 記憶體中定義空間作為緩衝區的,執行效率也較低;使用 Buffer.allocateDirect() 就是在本地記憶體中定義的。如果本地記憶體的可用空間不足以支撐需要分配的空間,就會排除 Direct buffer memory 的異常。具體演示案例可以執行下面程式碼:
public class DirectBufferOOM { public static void main(String[] args){ System.out.println("最大直接記憶體大小" + (sun.misc.VM.maxDirectMemory()/1024/1024) + "MB"); ByteBuffer.allocateDirect(20*1024*1024); } }
執行前需要將直接記憶體的大小設定為 6m :-XX:MaxDirectMemorySize=6m。
OOM:unable to create new native thread
當前應用程式建立過多的執行緒,超過設定的限制,就會丟擲異常。這個異常一般是在 linux 環境下產生的,windows 下預設是無限制的,linux 下非 root 使用者預設為 1024 個,執行下面程式碼就會丟擲此異常。
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(Exception e) {e.printStackTrace();} },""+i).start(); } } }
如果想要提高上限,除了切換 root 使用者外,還可以編輯 /etc/security/limits.d/90-nproc.conf ,增加當前使用者的名字,為其設定可以建立的執行緒數
OOM:Metaspace
元空間空間不足。因為在 JDK8 開始方法區實現變成了元空間,所以當建立了過多的類時,就會丟擲這個異常。
觸發案例:
public class MetaspaceOOM { static class OOMTest{} public static void main(String[] args){ int i = 0; try { while (true){ i++; 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.invokeSuper(o, args); } }); enhancer.create(); } } catch (Throwable throwable) { System.out.println("執行了" + i + "次"); throwable.printStackTrace(); } } }
Enhancer 是 Spring cglib中用於生成動態代理的類,可以為未實現介面的類建立代理。在上面程式碼中就是通過 Enhancer 不停地建立代理物件(建立代理物件的同時也會將代理類載入到方法區中)來模擬元空間不足的場景。為了現象更明顯,可以將元空間大小設定得小一些:-XX:MetaspaceSize=15m -XX:MaxMetaspaceSize=15