JVM系列 - 内存异常的代码实践
Last updated: Oct 289, 28089
Java 堆溢出
条件
- 不断地创建对象
- 保证 GC Roots 到对象之间可达,避免垃圾回收
- 通过
-Xmx
和-Xms
设置堆的最大值和最小值
代码与报错
/**
* VM Args: -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
*/
public class HeapOOM {
static class OOMObject {
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while (true) {
list.add(new OOMObject());
}
}
}
java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid6584.hprof … Heap dump file created [12984164 bytes in 0.190 secs]
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210) at java.util.Arrays.copyOf(Arrays.java:3181) at java.util.ArrayList.grow(ArrayList.java:261) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) at java.util.ArrayList.add(ArrayList.java:458) at chapter2.HeapOOM.main(HeapOOM.java:21)
解决
内存泄漏
查看泄漏对象到 GC Roots 的引用链,找到垃圾回收器无法回收它们的原因
内存溢出
查看 JVM 的堆参数 -Xmx
和 -Xms
的设置是否合理,对象的生命周期是否不当
JVM 栈 & 本地方法栈溢出
条件
- 通过
-Xss
减少栈内存容量 - 定义大量局部变量,增大方法 栈帧局部变量表 的大小
代码与报错
/**
* VM Args: -Xss160k
*/
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) throws Throwable {
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
} catch (Throwable e) {
System.out.println("stack length: " + oom.stackLength);
throw e;
}
}
}
stack length: 773
Exception in thread “main” java.lang.StackOverflowError
at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13)
at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
public class JavaVMStackSOF {
private static int stackLength = 0;
public static void test() {
long unused1, unused2, unused3, unused4, unused5;
stackLength++;
test();
unused1 = unused2 = unused3 = unused4 = unused5 = 0;
}
public static void main(String[] args) {
try {
test();
} catch (Error error) {
System.out.println("stack length: " + stackLength);
throw error;
}
}
}
stack length: 14990
Exception in thread “main” java.lang.StackOverflowError
at chapter2.JavaVMStackSOF.test(JavaVMStackSOF.java:15)
at chapter2.JavaVMStackSOF.test(JavaVMStackSOF.java:15)
at chapter2.JavaVMStackSOF.test(JavaVMStackSOF.java:15)
at chapter2.JavaVMStackSOF.test(JavaVMStackSOF.java:15)
at chapter2.JavaVMStackSOF.test(JavaVMStackSOF.java:15)
at chapter2.JavaVMStackSOF.test(JavaVMStackSOF.java:15)
at chapter2.JavaVMStackSOF.test(JavaVMStackSOF.java:15)
at chapter2.JavaVMStackSOF.test(JavaVMStackSOF.java:15)
/**
* VM Args: -Xss2M,在我的 64 位机器上没有复现出 unable to create native thread
*/
public class JavaVMStackOOM {
private void dontStop() {
while (true) {
}
}
public void stackLeakByThread() {
while (true) {
Thread thread = new Thread(() -> {
dontStop();
});
thread.start();
}
}
public static void main(String[] args) {
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackLeakByThread();
}
}
解决
一般从堆栈信息中,容易分析出问题所在
注意:可以适当减少 最大堆 和 栈容量 的方式来换取更多的线程
方法区 & 运行时常量池溢出
条件
- 在 JDK 8 之后,运行时常量池放到了 Java 堆中,因此限制 Java 堆大小
代码与报错
/**
* VM Args: -Xmx6m
*/
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
short i = 0;
while (true) {
set.add(String.valueOf(i++).intern());
}
}
}
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
at java.util.HashMap.resize(HashMap.java:703)
at java.util.HashMap.putVal(HashMap.java:662)
at java.util.HashMap.put(HashMap.java:611)
at java.util.HashSet.add(HashSet.java:219)
/**
* VM Args: -XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
*/
public class JavaMethodAreaOOM {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> methodProxy.invokeSuper(o, objects));
enhancer.create();
}
}
static class OOMObject {
}
}
Exception in thread “main” java.lang.OutOfMemoryError: Metaspace
at net.sf.cglib.core.AbstractClassGenerator.create
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
at chapter2.JavaMethodAreaOOM.main(JavaMethodAreaOOM.java:19)
解决
可以通过 -XX:MaxMetaspaceSize
、-XX:MetaspaceSize
等等参数保护元空间
本机直接内存溢出
条件
- 通过设置
-XX:MaxDirectMemorySize
较小,达到内存溢出的目的
代码与报错
/**
* VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M 然而没有复现 OOM
*/
public class DirectMemoryOOM {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true) {
unsafe.allocateMemory(_1MB);
}
}
}
解决
直接内存的大小可以通过 -XX:MaxDirectMemorySize
指定
默认与 Java 堆最大值一致