JVM 常見錯誤彙總

棧內存溢出

棧內存錯誤包括:棧幀過多(StackOverflowError)、棧幀過大(OutOfMemoryError)

  • StackOverflowError:如果線程請求的棧深度大於虛擬機所允許的最大深度;
  • OutOfMemoryError:如果虛擬機的佔內存允許動態擴展,當擴展容量無法申請到足夠的內存時;

棧幀過多

/**
 * 棧幀過多導致內存溢出演示
 * java.lang.StackOverflowError
 * 
 * -Xss256k 設置棧內存大小
 */
public class StackOverflowDemo {

    private static int count;

    public static void main(String[] args) {
        try {
            method1();
        }catch (Throwable e){
            e.printStackTrace();
            System.out.println(count);
        }

    }

    private static void method1() {
        count++;
        method1();
    }

}

棧幀過大

/**
 * 棧幀過大導致內存溢出演示 
 * Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
 * 
 * -Xss256k 設置棧內存大小
 *
 *《Java虛擬機規範》明確允許Java虛擬機實現自行選擇是否支持棧的動態擴展, 而HotSpot虛擬機的選擇不支持擴展;
 * 結論:無論式由於棧幀太大還是虛擬機容量太小, 當新的棧幀內存無法分配的時候, HotSpot虛擬機拋出的都是StackOverflowError異常; 
 * 若在允許動態擴展棧容量大小的虛擬機上, 結果就會導致不一樣的錯誤; 如Classic虛擬機, 則會出現java.lang.OutOfMemoryError;
 * 若測試不限於單線程, 通過不斷建立線程的方式, 在HotSpot也可以產生內存溢出異常;代碼如下, 現象不容易出現;
 * 注: Java的線程時映射到操作系統的內核線程上的, 因此代碼執行時有較大風險, 可能會導致操作系統假死;
 */
public class Demo {

    private void dontStop() {
        while (true) {
        }
    }

    public void stackLeakByThread() {
        while (true) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }

    public static void main(String[] args) throws Throwable {
        Demo oom = new Demo();
        oom.stackLeakByThread();
    }
}

堆內存溢出

堆內存溢出包括:Java堆(Heap)內存溢出、元空間內存溢出、運行時常量池溢出、本機直接內存溢出;

Java 堆內存溢出

/**
 * 演示堆內存溢出
 * java.lang.OutOfMemoryError: Java heap space
 *
 * -Xmx8m
 */
public class Demo {

        public static void main(String[] args) {
            int i = 0;
            try {
                List<String> list = new ArrayList<>();
                String a = "hello";
                while (true) {
                    list.add(a); // hello, hellohello, hellohellohellohello ...
                    a = a + a;  // hellohellohellohello
                    i++;
                }
            } catch (Throwable e) {
                e.printStackTrace();
                System.out.println(i);
            }
        }
}

方法區溢出:永久代內存溢出、元空間內存溢出

  • 永久代內存溢出(JDK1.6)
演示永久代內存溢出 java.lang.OutOfMemoryError: PermGen space
-XX:MaxPermSize=8m
    
    import com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter;
import com.sun.xml.internal.ws.org.objectweb.asm.Opcodes;
  • 元空間內存溢出(JDK1.8)
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;

/**
 * 演示元空間內存溢出 
 * 
 * java.lang.OutOfMemoryError: Metaspace
 *
 * -XX:MaxMetaspaceSize=8m
 */
public class Demo extends ClassLoader { // 可以用來加載類的二進制字節碼
    public static void main(String[] args) {
        int j = 0;
        try {
            Demo test = new Demo();
            for (int i = 0; i < 10000; i++, j++) {
                // ClassWriter 作用是生成類的二進制字節碼
                ClassWriter cw = new ClassWriter(0);
                // 版本號, public, 類名, 包名, 父類, 接口
                cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
                // 返回 byte[]
                byte[] code = cw.toByteArray();
                // 執行了類的加載
                test.defineClass("Class" + i, code, 0, code.length); // Class 對象
            }
        } finally {
            System.out.println(j);
        }
    }
}

直接內存溢出

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

/**
 * 演示直接內存溢出
 *
 * -Xmx6M
 */
public class Demo {
    static int _100Mb = 1024 * 1024 * 100;

    public static void main(String[] args) {
        List<ByteBuffer> list = new ArrayList<>();
        int i = 0;
        try {
            while (true) {
                ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb);
                list.add(byteBuffer);
                i++;
            }
        } finally {
            System.out.println(i);
        }
        // 方法區是jvm規範
        // jdk6 中對方法區的實現稱爲永久代
        // jdk8 對方法區的實現稱爲元空間
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章