JVM(2)-Java記憶體區域與記憶體溢位異常

Zouxxyy發表於2019-03-22

執行時資料區域

JVM(2)-Java記憶體區域與記憶體溢位異常
我們這節關注中間灰色部分,注意綠色部分執行緒共享,黃色部分執行緒私有

程式計數器

一塊較小的記憶體,是當前執行緒(執行緒私有)執行的位元組碼的行號指示器

  • 如果執行的是Java方法,計數器記錄的是正在執行的虛擬機器位元組碼指令的地址;如果是Native(本地方法),計數器值為空(Undefined)
  • 唯一沒有OutOfMemoryError的區域

Java虛擬機器棧

虛擬機器棧是描述Java方法執行的記憶體模型

JVM(2)-Java記憶體區域與記憶體溢位異常
每個方法呼叫至完成,對應一個棧幀在虛擬機器棧中入棧到出棧的過程。

區域性變數表:

  • 存放基本資料型別物件引用returnAddress
  • 區域性變數表所需的記憶體空間在編譯期完成,該空間是確定的,方法執行期間不改變

兩種異常:

  • StackOverflowError: 請求的棧深度超過允許範圍
  • OutOfMemoryError: 需要的記憶體超過允許範圍

本地方法棧

本地方法棧是描述本地方法執行的記憶體模型。它和Java虛擬機器棧很類似,區別就是它是為本地方法服務的。

Java堆

存放物件示例,也稱作(GC堆),是垃圾回收器管理的主要區域。

  • 細分 新生代、老生代;Eden空間、From Survivor空間、To Survivor空間等
  • 執行緒共享的堆裡可能劃分出私有的分配緩衝區(TLAB)。

方法區

儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯後的程式碼等資料。

執行時常量池 : 存放編譯器生成的各種字面常量和符號引用。

例子:String類在常量池的資料結構類似於HashSet,是唯一的。

JVM(2)-Java記憶體區域與記憶體溢位異常

String s1 = "abc";
String s2 = "abc"; // 存放在方法區的常量池(位元組碼常量)唯一

System.out.println(s1 == s2); // true

String s3 = new String("abc"); // 用了new,所以在堆中建立

System.out.println(s1 == s3); // false

System.out.println(s1 == s3.intern()); // true
複製程式碼

注意哦jdk1.8 String常量池搬到了堆中。如果想對intern()瞭解更多,可以看我的這篇部落格

HotSpot物件揭祕

物件建立

物件建立簡單描述:

new -> 根據常量池中符號看是否需要載入 -> 類載入 -> 分配記憶體 -> 初始化 ->構造方法

給物件分配記憶體的兩種方法:

  • 指標碰撞
  • 空閒列表

解決建立物件時執行緒安全問題兩種方法:

  • 分配記憶體動作進行同步處理
  • TLAB(共享堆中按執行緒劃分的執行緒私有部分)上分配,它分配完,再同步鎖定

物件的記憶體佈局

3塊區域:

  • 物件頭

    • 自身執行時的資料(MarkWord):HashCode、GC分代年齡、鎖狀態標誌、執行緒持有鎖、偏向執行緒ID、偏向時間戳等
    • 型別指標:物件指向它的類後設資料的指標,虛擬機器可以根據它來確定該物件是哪個類的例項。

    注意如果物件是陣列,那麼物件頭中還有一塊用於記錄陣列長度的資料。

  • 例項資料

  • 對齊填充 :當例項部分沒對齊時,通過對齊填充來補全

物件的訪問定位

  • 控制程式碼訪問(2級指標)

JVM(2)-Java記憶體區域與記憶體溢位異常
可以看出來要到例項資料必須再經過一個指標,這麼做到好處是,當物件移動時,我們只需要改變局柄池裡的指標,不需要改變reference;缺點是速度慢。

  • 直接指標

JVM(2)-Java記憶體區域與記憶體溢位異常
直接指標顧名思義直接指向例項資料,好處是速度快;缺點就是物件移動要改reference。

相關文章