java學習-jvm-記憶體分配
阿新 • • 發佈:2020-10-20
- 參考文章
- https://www.geeksforgeeks.org/java-memory-management/
- https://www.tutorialspoint.com/Java-JVM-Memory-Types
- https://javapapers.com/core-java/java-jvm-memory-types/
- https://www.netjstech.com/2017/10/jvm-run-time-data-areas-java-memory-allocation.html#ConstantPool
- https://javapapers.com/core-java/java-jvm-run-time-data-areas/#:~:text=In%20general%2C%20method%20area%20is%20a%20logical%20part,JVM%20startup%20and%20shared%20among%20all%20the%20threads
- java記憶體結構
- 堆(heap)
- 記憶體中共享的執行時資料區域,用於儲存例項化的java物件,當jvm啟動時,該區域會被例項化
- 在當前區域中只會存在java class物件以及陣列資料,對於堆的空間大小可以通過啟動引數 設定實現固定或動態
- Jvm提供初始化和修改堆空間大小的使用者控制;當 new 關鍵字在使用時,例項化的物件會被存放在堆空間中,但對於當前的物件的引用資料,會被儲存在棧中
- 每個jvm有且僅有一個heap區域
- 對於 heap區域gc是強制的
- Heap實際是由 eden + survivor(0/1) + oldGeneration
- 方法區(method area):實際也是屬於heap的
- 屬於heap中的邏輯區域,在jvm啟動時建立
- 為class 結構/方法資料/構造器欄位資料 分配的記憶體區域,空間大小也可以是固定或動態的;
- 可以固定大小或動態擴容, 無需是連續的記憶體空間
- 雖然 method area 屬於 heap,但對於當前區域gc並非是強制的
- 對於 run-time constant pool 是屬於 method-area:主要是儲存 編譯時常量資料(字串和數字)以及執行時的方法以及欄位引用
- 棧(stack)
- 線上程建立時,棧也會隨之建立,棧是屬於執行緒的私有區域;當方法存在返回值和動態連結庫,被用來儲存資料和部分結果
- 棧空間可以是固定的或動態的,也可以通過java程式碼在建立執行緒時來指定棧空間大小
- 棧的記憶體空間無需是連續記憶體
- 程式語言中的棧定義
- 對於呼叫棧也可以稱為棧,呼叫者會將返回的地址壓入棧中然後呼叫下一個執行,當他完成時,呼叫棧的結果有可能出棧也有可能入棧以及轉移指定地址的控制權;如果呼叫其他的行為時,則會將返回的地址寫入呼叫的棧中;對於程式的執行就是出棧和入棧的結果
- 對於高階語言中,特殊點在於對於呼叫棧的操作是隱藏的,只會提供相關的操作方法,而不允許直接操作記憶體
- java 中的棧
- 每個jvm執行緒都會擁有一個私有的棧空間,在建立執行緒時同步建立當前執行緒的棧空間;
- jvm棧中儲存的是幀(資料控制代碼)
- jvm棧和c的棧一致,都是儲存方法區域性變數和執行時結果;
- 由於jvm中棧空間絕對不會被直接操作;堆分配棧幀
- jvm棧的空間不需要是連續記憶體
- 本地方法棧(native method stack)
- 也可以成為C語言棧,native method並不是由java程式碼生成,其記憶體只和執行緒關聯,線上程建立時會分配記憶體, 也可以固定大小或動態
- program counter registers
- 每個執行特殊方法任務的jvm執行緒都會存在一個 程式計數器暫存器相關聯;對於存在程式計數器的非native方法會儲存有效的JVM指令地址,而 對於native方法中,該程式計數器的值時未定義的;
- 在一些特殊的平臺中,程式計數器暫存器支援儲存返回資料的記憶體地址或一個 native 指標
- 非堆(non-heap)
- Metaspace : 元資料 ,一般包含一些class 資料
- code cache:
- 對於code cache首先需要了解 java編譯過程,以及jit
- 對於一個java程式碼的執行過程; 首先 前端編譯(javac): .java -> .class ;對於.class的執行並不能直接被cpu識別;
- 此時就引出了後端編譯器(執行 class 資料)
- 解釋執行: 對位元組碼進行逐行解釋執行,執行效率較低
- 編譯執行:在第一次正式執行前,將class翻譯為機器碼,供cpu執行呼叫;優點是執行效率高,但缺點是第一次編譯為機器碼時效率較低
- 分層編譯:
- https://docs.oracle.com/en/java/javase/14/vm/java-hotspot-virtual-machine-performance-enhancements.html#GUID-3BB4C26F-6DE7-4299-9329-A3E02620D50A
- 對於不同的 client/server vm 實際提供了不同的compiler,其中client 適合啟動時編譯,而server 適合在執行時使用;
- 因此分層編譯實際就是在啟動時,對啟動必須的,優先使用client compiler(c1);而在執行時則會使用server compiler(c2)
- 對於編譯後的機器碼資料,此時就會儲存在codecache區域; 但在該區域不止包含jit編譯後的機器碼,也包含 jni (native)程式碼
- 原生型別資料儲存
- 對於 編譯時初始化的原生資料(int等),會在編譯時存放到run-time constant pool中;
- 對於方法引數中的原生資料是該方法執行時壓入棧中的;
- 對於類的屬性,會在當前類例項化時直接存放在當前物件例項所在的堆區域
public class JVMPrimaryDataDemo { // 對於當前常量資料,在類載入期間時將class檔案轉為Class物件後,初始化時會將當前資料存放到run-time constant pool 區域 public static final int CONSTANT_COMPILER_DATA = 1; public int j = 4; // 對於該欄位的資料是存放在構造方法中的;是存在於例項化物件中(堆) public static void main(String[] args) { // 而對於方法中的資料在執行時會被直接載入到棧中 int i = 3; // 該數字在編譯後會被翻譯為 iconst_數值 } /** * 對 編譯後的class檔案 使用javap -v 解析 * // 常量池 == run-time constants pool * Constant pool: * #1 = Methodref #2.#3 // java/lang/Object."<init>":()V * #2 = Class #4 // java/lang/Object * #3 = NameAndType #5:#6 // "<init>":()V * #4 = Utf8 java/lang/Object * #5 = Utf8 <init> * #6 = Utf8 ()V * #7 = Class #8 // com/xing/level/JVMPrimaryDataDemo * #8 = Utf8 com/xing/level/JVMPrimaryDataDemo * #9 = Utf8 CONSTANT_COMPILER_DATA // 欄位名 * #10 = Utf8 I * #11 = Utf8 ConstantValue * #12 = Integer 1 // 宣告的常量資料 * #13 = Utf8 Code * #14 = Utf8 LineNumberTable * #15 = Utf8 LocalVariableTable * #16 = Utf8 this // 當前物件引用 * #17 = Utf8 Lcom/xing/level/JVMPrimaryDataDemo; * #18 = Utf8 main // 方法名 * #19 = Utf8 ([Ljava/lang/String;)V * #20 = Utf8 args // 引數名 * #21 = Utf8 [Ljava/lang/String; * #22 = Utf8 i // 區域性變數名 * #23 = Utf8 SourceFile * #24 = Utf8 JVMPrimaryDataDemo.java */ }