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=15m。

Tags: