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 对方法区的实现称为元空间
}
}