-
深入理解Java虚拟机
-
引言
本文内容总结自《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》中的第2章。
- 通过设置虚拟机的启动参数来模拟内存区域溢出的情况
- 通过异常信息判断出是哪个内存溢出了,知道什么样的代码可能会导致内存溢出
-
设置虚拟机的启动参数
在使用代码进行验证之前,我们需要先了解如何设置虚拟机的启动参数,设置方法有两种。
使用命令行执行程序时
使用命令行运行程序时,直接在java后面跟上参数和程序名称
D:\>javac HeapOOM.java
D:\>java HeapOOM
D:\>java -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError HeapOOM
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid9512.hprof ...
Heap dump file created [27898328 bytes in 0.121 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2245)
at java.util.Arrays.copyOf(Arrays.java:2219)
at java.util.ArrayList.grow(ArrayList.java:242)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)
at java.util.ArrayList.add(ArrayList.java:440)
at HeapOOM.main(HeapOOM.java:16)
-
使用IDE执行程序时
直接在VM Arguments中设置
-
Java堆溢出
-
VM arguments
设置Java堆的大小20MB,将堆的最小值-Xms参数和最大值-Xmx参数设置为一样,避免堆自动扩展。通过设置参数-XX:+HeapDumpOnOutOfMemoryError,在发生内存异常时可以生成堆转存储的dump文件,通过Eclipse Memory Analyzer (MAT)工具可以分析该文件。
VM Args:
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
Option | Description |
---|---|
-Xms20M -Xmx20M | to specify that a maximum heap size of 20 Mbytes |
-XX:+HeapDumpOnOutOfMemoryError | Dump heap to file when java.lang.OutOfMemoryError is thrown |
-
Code
根据Java内存区域可知,通过new指令创建的对象是放到Java堆中的。下面这段代码的功能就是一直在创建OOMObject对象,当OOMObject对象占用大小超出设置的20M时,就会报出OutOfMemoryError异常,并提示发生异常的区域为"Java heap space"
import java.util.ArrayList;
import java.util.List;
/**
* VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*/
public class HeapOOM {
static class OOMObject {
}
public static void main(String[] agrs) {
List<OOMObject> list = new ArrayList<OOMObject>();
while (true) {
list.add(new OOMObject());
}
}
}
-
Output
发生异常后生产的dump文件的默认名称为"java_pid进程id.hprof",默认生成路径为工程的根目录
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid9512.hprof ...
Heap dump file created [15782234 bytes in 0.074 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2760)
at java.util.Arrays.copyOf(Arrays.java:2734)
at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
at java.util.ArrayList.add(ArrayList.java:351)
at HeapOOM.main(HeapOOM.java:16)
使用MAT打开 java_pid9512.hprof,可以生成一个分析报告
点击进入Dominator Tree界面,可以将对象占用Heap大小进行排序,由下图可以看出main thread里面生成了很多HeapOOM$OOMObject对象,导致暂用了97.49%的内存。
-
虚拟机栈溢出
-
VM arguments
使用-Xss参数减少栈内存的容量
VM Args:
-Xss128k
-
Code
stackLeak()方法不断地自己调用自己,导致栈帧的深度一直在增加,当超出栈内存容量时,将会抛出java.lang.StackOverflowError异常
/**
* VM Args:-Xss128k
*/
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
/**
* @param args
* @throws Throwable
*/
public static void main(String[] args) throws Throwable {
// TODO Auto-generated method stub
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
} catch (Throwable e) {
// TODO: handle exception
System.out.println("stack length : " + oom.stackLength);
throw e;
}
}
}
- Output
stack length : 11432
Exception in thread "main" java.lang.StackOverflowError
at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:8)
at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:9)
at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:9)
... ...
-
方法区和运行时常量池异常
运行时常量池是方法区的一部分,而方法区属于"永久代"(PermGen space)
-
VM arguments
通过-XX:PermSize和 -XX:MaxPermSize限制永久代的大小
VM Args:
-XX:PermSize=10M -XX:MaxPermSize=10M
-
Code
String.intern()的作用:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回常量池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。
下面这段代码一直在往常量池中添加字符串,直到超出常量池的容量。
import java.util.ArrayList;
import java.util.List;
/**
* VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
*/
public class RuntimeConstantPoolOOM {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
List<String> list = new ArrayList<String>();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}
- Output
JDK 1.6及之前的版本中,常量池分配在永久代内,可以通过-XX:PermSize和 -XX:MaxPermSize限制永久代的大小,当常量池超出容量时,会抛出异常"Exception in thread "main" java.lang.OutOfMemoryError: PermGen space"
由于JDK 1.7的HotSpot中,已经把原本放在永久代中的字符串常量池移除,因此使用JDK 1.7运行这段代码时不会抛出异常,while循环将一直进行下去
-
本机直接内存溢出
-
VM arguments
DirectMemory容量可以通过MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值一样(-Xmx指定)
VM Args:
-Xmx20M -XX:MaxDirectMemorySize=10M
-
Code
通过Unsafe对象的allocateMemory方法申请直接内存,当超出DirectMemory容量时,抛出异常java.lang.OutOfMemoryError
import java.lang.reflect.Field;
import sun.misc.Unsafe;
/*
* VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M
*/
public class DirectMemoryOOM {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws IllegalArgumentException,
IllegalAccessException {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true) {
unsafe.allocateMemory(_1MB);
}
}
}
- Output
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at DirectMemoryOOM.main(DirectMemoryOOM.java:16)
-
参考
- 深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)
- Configuring the Default JVM and Java Arguments
- Java HotSpot VM Options