我把Java的内存区域画了一张思维导图,以及各区域的主要功能。
模拟Java堆溢出
Java堆用于存储对象实例。仅仅要不断地创建对象而且保证GC ROOTS到对象之间有可达路径避免被回收机制清除。就能够模拟出Java堆溢出。
package hxl.insist.jvm;
import java.util.ArrayList;
import java.util.List;
/**
* 以下是JVM Args:
* -Xms20m 堆的最小值 -Xmx20m 堆的最小值 (设置为一样可避免堆自己主动扩展)
* -XX:+HeapDumpOnOutOfMemoryError 当虚拟机出现内存溢出异常时,Dump出当前的堆转储快照
* -XX:HeapDumpPath=E:\eclipseworkspace\UnderStandingTheJVM\hprof 设置生成的堆转储快照的路径
* @author hanxl
*
*/
public class HeapOutOfMemory {
static class StuffObject {
}
static List<StuffObject> list = new ArrayList<StuffObject>();
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
public void run() {
createObj();
}
});
thread.start();
}
private static void createObj() {
while (true) {
list.add(new StuffObject());
}
}
}
用MemoryAnalyzer分析一下堆转储快照例如以下图:
从根元素到内存消耗聚集点的最短路径,能够非常清楚的看到整个引用链。
在上面这张图上,我们能够清楚的看到,这个对象集合中保存了大量内部类StuffObject 对象的引用,就是它导致的内存泄露。
模拟Java虚拟机栈溢出
关于虚拟机栈,在Java虚拟机中规范了两种异常:
- 假设线程请求的栈深度大于虚拟机所同意的最大深度,将抛出*Error异常。
- 假设虚拟机地扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。
模拟第一种情况:
package hxl.insist.jvm;
/**
* 以下是JVM Args:
* -Xss128k 设置栈容量大小
* @author hanxl
*/
public class JavaVMStackSOF {
public void stackLeak() {
stackLeak();
}
public static void main(String[] args) throws Throwable {
new JavaVMStackSOF().stackLeak();
}
}
模拟另外一种情况:
package hxl.insist.jvm;
/**
* 以下是JVM Args:
* -Xss2M 设置栈容量大小
* @author hanxl
*/
public class JavaVMStackOOM {
public static void main(String[] args) {
new JavaVMStackOOM().threadInvokeMethod();
}
public void threadInvokeMethod() {
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
infiniteLoop();
}
});
thread.start();
}
}
private void infiniteLoop() {
while (true)
;
}
}
比較两种情况为什么实现方式不同?
Java虚拟机栈是线程私有的,它的生命同期与线程同样。我们把虚拟机栈比做一个盒子,-Xss是设置盒子的大小,而一个线程仅仅能相应一个盒子。而每一个Java方法在运行的时候都会在盒子中创建一个栈帧用于存储局部变量表等一些信息。
所以为了制造出第一种情况下的异常,我们把盒子的大小设置小一点,使用递归不断调用方法,从而撑破盒子。而为了制造出另外一种情况下的异常,我们应该把盒子的大小设置小大一点,多创建一些盒子,从而让其无法申请到足够的内存空间。
仅仅要了解上面那张思维导图的内存区域,模拟出其他内存区域异常也非常easy。