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 对方法区的实现称为元空间
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章