詳解JVM系列之內存模型
1. 內存模型和運行時數據區
這一章學習java虛擬機內存模型(Java Virtual machine menory model),可以這樣理解,jvm運行時數據庫是一種規范,而JVM內存模型是對改規范的實現
java虛擬機重點存儲數據的是堆和方法區,所以本章節也重點從這兩個方面進行比較詳細描述。堆和方法區是內存共享的,而java虛擬機棧、Native方法棧、程序計數器是線程私有的
2、思維導圖和圖例
一個是非堆區(方法區),方法區也一般被稱之為“永久代”。另外一個是堆區,分為young區和old區,young區又分為兩個部分,一個是Eden區,一個是Survivor區(S0+S1),S0區也可以稱之From區,S1也可以稱之為To區
3、對象向JVM申請空間
4、為什麼需要Survivor區?
為什麼需要Survivor區?隻有Eden不行嗎?
假設不設計出Survivor區,Eden區進行一次MinorGC,對象就直接被送到Old區,這樣一來Old區很快就被填滿,Old區一滿,就會進行FullGC(Old區會進行MajorGC,一般伴隨著MinorGC),FullGC是很耗時的,所以設計出Survivor區的目的是減少對象被送到Old區,有一個過渡的Survivor區
補充:Minor GC:新生代
Major GC:老年代
Full GC:新生代+老年代
Eden:S1:S2是8:1:1
5、為什麼需要兩個Survivor區?
需要兩個Survivor區的目的是為瞭避免內存碎片化。為什麼這麼說?
假設隻設計出一個Survivor區,一旦Eden區滿瞭,就會進行Minor GC,Eden區存活的對象就會被移動到Survivor區,等下一次Eden區滿時候,問題就來瞭,進行MinorGC就將Eden區對象硬放到Survivor區,這樣就導致瞭對象所占的內存是不連續的
6、例子進行驗證
堆內存溢出
import lombok.Data; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @RestController public class HeapController { List<Foo> list = new ArrayList<Foo>(); @GetMapping(value = {"heap"}) public String heapTest() { while (true) { list.add(new Foo()); } } @Data class Foo { String str; } }
訪問接口,出現內存溢出;
java.lang.OutOfMemoryError: Java heap space
…
可以設置參數:比如-Xms64M -Xmx512M
方法區內存溢出
使用asm,maven配置:
<dependency> <groupId>asm</groupId> <artifactId>asm</artifactId> <version>3.3.1</version> </dependency>
編寫代碼,向方法區中添加Class的信息,註意,電腦性能不夠好,不要執行此代碼,很容易,造成電腦重啟,太吃內存,也可以調小循環次數
import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.util.ArrayList; import java.util.List; public class MyMetaspace extends ClassLoader { public static List<Class<?>> createClasses() { List<Class<?>> classes = new ArrayList<Class<?>>(); for (int i = 0; i < 10000000; ++i) { ClassWriter cw = new ClassWriter(0); cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null); MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); mw.visitVarInsn(Opcodes.ALOAD, 0); mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); mw.visitInsn(Opcodes.RETURN); mw.visitMaxs(1, 1); mw.visitEnd(); MyMetaspace test = new MyMetaspace(); byte[] code = cw.toByteArray(); Class<?> exampleClass = test.defineClass("Class" + i, code, 0, code.length); classes.add(exampleClass); } return classes; } }
方法區測試接口:
import com.example.jvm.jvmexceptionexample.asm.MyMetaspace; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @RestController public class NonHeapController { List<Class<?>> list = new ArrayList<Class<?>>(); @GetMapping(value = {"/noheap"}) public String noheap() { while (true) { list.addAll(MyMetaspace.createClasses()); } } }
java.lang.OutOfMemoryError: Metaspace
at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.5_54]
處理方法,設置Metaspace的大小,比如-XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=512M
Java虛擬機棧
在前面學習,java虛擬機棧是通過棧幀方式存儲,一個方法對應一個棧幀,按照隊列模式進棧,所以要測試程序導致java虛擬機棧出現問題,可以通過遞歸方法方式進行測試:
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class StackController { public static long count = 0; public static void add(long i) { count ++ ; add(i); } @GetMapping(value = {"stack"}) public void stack() { add(1); } }
StackOverflow,棧溢出異常:
java.lang.StackOverflowError: null
at com.example.jvm.jvmexceptionexample.controller.StackController.add(StackController.java:14) ~[classes/:na]
處理方法,設置-Xss256k:設置每個線程的堆棧大小。JDK 5以後每個線程堆棧大小為1M,以前每個線程堆棧大小為256K
以上就是詳解JVM系列之內存模型的詳細內容,更多關於JVM 內存模型 內存結構的資料請關註WalkonNet其它相關文章!