1. 程式人生 > >JVM 中的異常

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