關於方法區、堆和虛擬機棧我們已經知道了它們的職責。
這裏會有個疑惑的地方:
方法區存放的是類的元信息、靜態變量和常量,存滿了怎麼辦?
堆存放的是對象,存滿了怎麼辦?
虛擬機棧會進行一系列方法的壓棧出棧,棧滿了怎麼辦?
其實好辦,這就是所謂的內存溢出了。
構造堆內存溢出
這個其實很好構建,寫個死循環代碼,然後不停的產生對象,把堆撐爆了,不就堆內存溢出了。
代碼
/**
* 爲了讓堆內存溢出快速暴露,這裏設置堆內存大小爲:-Xmx32M -Xms32M
* @return
*/
@RequestMapping("/heap")
public String heap(){
List<User> list=new ArrayList<>();
int i=0;
while (true){
list.add(new User(i++, UUID.randomUUID().toString()));
}
}
User對象就是一個普通對象,沒啥東西。
@Data
@AllArgsConstructor
public class User {
private Integer id;
private String name;
}
IDEA修改jvm堆內存大小,然後再啓動。
執行heap方法後,內存溢出的報錯降臨了。
構造方法區(Meta Space)溢出
自jdk1.8後,有了一個堆外內存,它是操作系統本地內存,一般用來存放class對象。一樣的道理,寫個死循環代碼,不停的產生class對象,把它撐爆。
代碼
/**
* 非堆內存溢出(Meta Space)
* 爲了讓非堆內存溢出快速暴露,這裏設置大小爲:-XX:MetaspaceSize=32M -XX:MaxMetaspaceSize=32M
* @return
*/
@RequestMapping("noheap")
public String noheap() {
List<Class<?>> list = new ArrayList<>();
int i = 0;
while (true) {
list.addAll(Metaspace.createClasses());
}
}
Metaspace是個工具類,用來產生class對象。
public class Metaspace extends ClassLoader {
public static List<Class<?>> createClasses() {
// 類持有
List<Class<?>> classes = new ArrayList<Class<?>>();
// 循環1000w次生成1000w個不同的類。
for (int i = 0; i < 10000000; ++i) {
ClassWriter cw = new ClassWriter(0);
// 定義一個類名稱爲Class{i},它的訪問域爲public,父類爲java.lang.Object,不實現任何接口
cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "Class" + i, null,
"java/lang/Object", null);
// 定義構造函數<init>方法
MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
"()V", null, null);
// 第一個指令爲加載this
mw.visitVarInsn(Opcodes.ALOAD, 0);
// 第二個指令爲調用父類Object的構造函數
mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object",
"<init>", "()V");
// 第三條指令爲return
mw.visitInsn(Opcodes.RETURN);
mw.visitMaxs(1, 1);
mw.visitEnd();
Metaspace test = new Metaspace();
byte[] code = cw.toByteArray();
// 定義類
Class<?> exampleClass = test.defineClass("Class" + i, code, 0, code.length);
classes.add(exampleClass);
}
return classes;
}
}
maven裏還需要引入asm依賴包。
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>
IDEA修改jvm堆內存大小,然後再啓動。
執行noheap方法後,堆外內存溢出也暴露出來了。
虛擬機棧溢出
弄一個遞歸方法 ,不斷的去壓棧導致棧溢出。
代碼
public class Test06 {
public static long count = 0;
public static void method(long i) {
System.out.println(count++);
method(i);
}
public static void main(String[] args) {
method(1);
}
}
執行完後,可見內存溢出。
這裏還可以將棧的深度設置小一點,這樣它的溢出速度就更快了。