1. 程式人生 > >JAVA虛擬機器(JVM)劃重點 第二章 Java記憶體區域與記憶體溢位異常 之 虛擬機器物件

JAVA虛擬機器(JVM)劃重點 第二章 Java記憶體區域與記憶體溢位異常 之 虛擬機器物件

本部落格參考《深入理解Java虛擬機器》(第二版)一書,提取重點知識,再加以個人的理解編寫而成。轉載請標明來源。

JAVA虛擬機器(JVM)劃重點 第二章 Java記憶體區域與記憶體溢位異常 之 虛擬機器物件

Java物件的建立

1、類載入過程

虛擬機器遇到一個new指令時,首先檢查這個指令的引數能否在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已被載入、解析和初始化過。若沒有,則進行類載入過程。
Java虛擬機器類載入過程

2、分配記憶體

在類載入檢查通過後,虛擬機器將為新生物件分配記憶體,物件所需的記憶體的大小在類載入後是完全確定的。所以這一步意味著在Java堆中劃出固定大小的記憶體分配給新生物件。記憶體分配有一下兩種方式:

  • 指標碰撞:這種方式的前提是Java堆記憶體是規整的,即用過的記憶體放一邊,空閒的記憶體放另一邊,中間有個指標作為界限,分配記憶體時只需要把指標向空閒的一邊移動物件所需的大小即可。
  • 空閒列表:如果Java堆中的記憶體並不規整,即已使用的記憶體和空閒的記憶體相互交錯,就無法進行指標碰撞了,虛擬機器必須維護一個列表,記錄哪些記憶體是空閒的,分配時從列表中找一塊足夠大的空間劃分給物件例項,並更新列表上的記錄。

至於選擇哪種分配方式,主要取決於Java堆是否規整,而Java堆是否規整又取決於垃圾收集器是否有壓縮整理的功能。

對記憶體的分配同時還要考慮併發情況下的執行緒安全問題,可能正在給物件A分配記憶體,指標還沒來得及修改,物件B又同時使用了原來的指標分配記憶體。針對這種情況有兩種方案:

  • 採用CAS配上失敗重試的方式保證更新操作的原子性。
  • 把記憶體分配的動作按照執行緒劃分在不同的空間中進行。即每個執行緒在Java堆中預先分配一小塊記憶體,成為本地執行緒分配緩衝(Thread Local Allocation Buffer, TLAB)。哪個執行緒要分配記憶體,就在哪個執行緒的TLAB上分配,只有TLAB用完並分配新的TLAB時,才需要同步鎖定。

3、記憶體初始化

記憶體分配完成後,虛擬機器需要將分配到的記憶體空間都初始化為零值(不包括物件頭)。如果使用了TLAB,則這一工作也可以提前至TLAB分配時進行。

這一操作保證了物件的例項欄位在Java程式碼中可以不賦初值就能直接使用,程式能訪問到這些欄位的資料型別所對應的零值。

4、物件設定

接下來,虛擬機器對物件進行必要的設定

  • 這個物件是哪個類的例項
  • 如何找到類的元資料資訊
  • 物件的雜湊碼
  • 物件的GC分代年齡
  • ……

這些資訊存放在物件的物件頭之中,根據虛擬機器當前的執行狀態的不同,如是否使用偏向鎖等,物件頭會有不同的設定方式。

5、執行方法

一般來說,執行new指令之後會接著執行方法,把物件按照程式設計師的意願進行初始化,這樣一個真正可用的物件才算

物件的記憶體佈局

物件在記憶體中的儲存佈局可以分為三個區域:物件頭(Header)、例項資料(Instance Data)和對齊填充(Padding)。

1、物件頭

物件頭分為兩部分資訊

  • 儲存物件自身的執行時資料:雜湊碼(HashCode)、GC分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向執行緒ID、偏向時間戳等,這部分資料的長度在32位和64位的虛擬機器(未開啟壓縮指標)中分別為32bit和64bit,官方稱為“Mark Word”。考慮到虛擬機器的空間效率,Mark Word被設計成一個非固定的資料結構,以便在極小的空間記憶體儲存儘量多的資訊。舉例說明一下,在32位的HotSpot虛擬機器中,如果物件屬於未被鎖定的狀態下,Mark Word的32bit空間中,25bit儲存物件的雜湊碼,4bit儲存物件的分代年齡,2bit用於儲存鎖標誌位,1bit固定為0。而在其他狀態(輕量級鎖定、重量級鎖定、GC標記、可偏向),物件的儲存內容的含義不同。希望下圖能幫您理解:
    在這裡插入圖片描述
  • 物件頭的另一部分是型別指標,即物件指向它的類元資料的指標,虛擬機器通過這個指標類確定這個物件是哪個類的例項。(其實並不是所有的虛擬機器實現都必須在物件資料上保留型別指標,即查詢物件元資料資訊並不一定要經過物件本身)。另外,如果物件是一個Java陣列,那麼在物件頭中還必須有一塊用於記錄陣列長度的資料(虛擬機器可以通過普通Java物件的元資料資訊確定Java物件的大小,但是從陣列的元資料中無法確定陣列的大小)。

2、物件例項資料

儲存物件例項資料

3、對齊填充部分

對齊填充部分並非必須存在。這部分沒有特別的含義,僅起佔位的作用,由於HotSpot虛擬機器的自動記憶體管理系統要求物件起止地址必須是8位元組的整倍數,物件頭部分正好是8位元組整倍數,當例項部分沒有對齊時,就需要佔位補齊。

物件的訪問定位

建立物件就是為了使用物件,Java程式需要通過棧上的reference資料來操作堆上的具體物件。主流的訪問方式有使用控制代碼直接指標兩種。

  • 如果使用控制代碼訪問,Java堆中將會劃分一塊記憶體作為控制代碼池,reference中儲存的就是物件的控制代碼地址,而控制代碼中包含了物件例項資料和型別資料各自的具體地址資訊。
    控制代碼方式訪問物件
  • 如果使用直接指標訪問,那麼Java堆物件的不居中就必須考慮如何放置訪問型別資料的相關資訊,而reference中儲存的就是物件地址。
    使用直接指標訪問

控制代碼訪問方式優勢:reference中儲存的控制代碼地址是穩定的,在物件被移動(垃圾收集時經常需要移動物件)時只需改變控制代碼中的例項資料指標,而reference本身不需要修改。

直接指標方位方式的優勢:速度更快,節省了一次指標定位的時間開銷。

兩種方式都很常用。