003-Java虛擬機JVM之內存模型

Java知識點總結系列目錄

類加載器將Class文件讀取後,放到運行時數據區,然後執行引擎執行或調用本地接口、本地庫。

在這裏插入圖片描述
1、方法區(元空間)

  • 線程共享

  • JDK1.8後叫元空間Metaspace,存儲在本地內存中
    JDK1.8前叫永久代PermGen,存儲在堆上

  • 存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。由JVM自己使用

  • 運行時常量池:運行時常量池是方法區的一部分,用於存放編譯期生成的各種 字面量 和 符號引用

在這裏插入圖片描述

  • 相關JVM參數設置

    -XX:PermSize 永久代初始大小(JDK1.7及以前)
    -XX:MaxPermSize 永久代最大大小(JDK1.7及以前)
    -XX:MetaspaceSize 元數據區初始值(JDK1.8)
    -XX:MaxMetaspaceSize 元數據區最大值(JDK1.8)

2、堆

  • 線程共享

    堆的唯一目的就是存放對象實例,幾乎所有的對象實例(和數組)都在這裏分配內存。由開發人員使用

    -Xms堆的最小值
    -Xmx堆空間的最大值

  • 新生代

    新生代分爲Eden(伊甸園區), S0(Survivor From), S1(Survivor To)三個區域。三個區域的默認大小比例爲8:1:1。S0和S1大小一致,在GC複製算法中,採用的回收算法是複製交換算法。From和To的角色將會放生交換,哪個區域爲空就表示該區域爲To區域

    該區域發生的GC叫MinorGC,也是最頻繁的垃圾回收區域。剛創建的對象都放入 eden,s0 和 s1 都至少經過一次GC並倖存。如果倖存對象經過一定時間仍存在,則進入老年代

    -XX:NewSize新生代的最小值
    -XX:MaxNewSize新生代的最大值
    -XX:NewRatio設置新生代與老年代在堆空間的大小
    -XX:SurvivorRatio新生代中Eden所佔區域的大小

  • 老年代

    老年代的空間默認是新生代的兩倍大小,也就是新生代:老年代=1:2。發生在該區域的GC叫FullGC或者MajorGC。採用的算法是標記-清除算法

3、線程棧(虛擬機棧)

  • 線程私有

    每個線程擁有一個自己的棧,這個棧的大小決定了方法調用的可達深度(遞歸多少層次,或嵌套調用多少層其他方法,-Xss 參數可以設置虛擬機棧大小),若線程請求的棧深度大於虛擬機允許的深度,則拋出 StackOverFlowError 。此外,棧的大小可以是固定的,也可以是動態擴展的,若虛擬機棧可以動態擴展(大多數虛擬機都可以),但擴展時無法申請到足夠的內存(比如沒有足夠的內存爲一個新創建的線程分配棧空間時),則拋出 OutofMemoryError

  • 棧幀

    每個方法在執行的時候都會創建一個棧幀,用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息,而且 每個方法從調用直至完成的過程,對應一個棧幀在虛擬機棧中入棧到出棧的過程。

    1、 局部變量表

    主要存放一些基本類型的變量(int, short, long, byte, float, double, boolean, char)和 對象句柄(reference引用),它們可以是方法參數,也可以是方法的局部變量

    reference引用方位對象實例有兩種方式

    句柄訪問:Java堆中會劃分出一塊內存作爲句柄池,棧中的reference指向對象的句柄地址,句柄中包含了對象實例數據和類型數據各自的具體地址信息
    在這裏插入圖片描述
    直接訪問:reference中存儲的就是對象地址

    在這裏插入圖片描述

    這兩種對象訪問方式各有優缺點。使用句柄訪問的最大好處就是reference中存儲的是穩定的句柄地址,對象被移動(垃圾收集時移動對象是非常普遍的行爲)時只會改變句柄中的實例數據指針,reference本身不需要修改;而使用直接指針訪問的最大好處就是速度快,節省了一次指針定位的時間開銷

    2、操作數棧

    一塊存放臨時操作數的內存區域,局部變量的賦值,運算等
    在這裏插入圖片描述
    JVM指令中每個指令前面的序號可以看做是程序計數器,下面詳細描述一下過程(局部變量的index是從0開始的,如果方法聲明中有參數,則序號從第一個參數開始0開始遞增,如果方法聲明中沒有參數,則方法內變量從序號1開始遞增)

      0: iconst_3 	//常量3壓入操作數棧
     1: istore_1	//彈出棧頂元素3賦值給方法中第一個局部變量a(出棧)
     2: iconst_4	//常量4壓入操作數棧
     3: istore_2	//彈出棧頂元素4賦值給方法中第二個局部變量b(出棧)
     4: iload_1		//將第一個局部變量a的值3壓入操作數棧
     5: iload_2		//將第二個局部變量b的值4壓入操作數棧
     6: iadd		//彈出棧頂兩個數據做加法然後壓棧
     7: iconst_5	//常量5壓入操作數棧
     8: imul		//彈出棧頂兩個元素做乘法後壓棧
     9: istore_3	//彈出棧頂元素35賦值給方法中第三個局部變量c(出棧)
    10: iload_3		//將第三個局部變量c的值入棧
    11: ireturn		//返回
    

    3、動態鏈接

    虛擬機運行的時候,運行時常量池會保存大量的符號引用,這些符號引用可以看成是每個方法的間接引用

    靜態解析:如果符號引用是在類加載階段或者第一次使用的時候轉化爲直接應用,那麼這種轉換成爲靜態解析。

    動態連接:如果是在運行期間轉換爲直接引用,那麼這種轉換就成爲動態連接。

    4、方法返回地址

    方法是正常退出的,則調用者的PC計數器的值就可以作爲返回地址;

    方法異常退出的,則是需要通過異常處理表來確定。

4、本地方法棧

  • 線程私有

本地方法棧(Native MethodStacks)與虛擬機棧所發揮的作用是非常相似的,其區別不過是虛擬機棧爲虛擬機執行Java 方法(也就是字節碼)服務,而本地方法棧則是爲虛擬機使用到的Native 方法服務。虛擬機規範中對本地方法棧中的方法使用的語言、使用方式與數據結構並沒有強制規定,因此具體的虛擬機可以自由實現它。甚至有的虛擬機(如Sun HotSpot 虛擬機)直接就把本地方法棧和虛擬機棧合二爲一。

與虛擬機棧一樣,本地方法棧區域也會拋出StackOverflowError和OutOfMemoryError異常。

5、程序計數器

  • 線程私有

  • 概念: 線程私有的一塊較小的內存空間,可以看做是當前線程所執行的字節碼的行號指示器

  • 作用: 爲了線程切換後能夠恢復到正確的執行位置,每條線程都需要一個獨立的程序計數器去記錄其正在執行的字節碼指令地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章