1. 程式人生 > >Java 記憶體區域與記憶體溢位異常(二)

Java 記憶體區域與記憶體溢位異常(二)

一、虛擬機器中Java物件的建立

語言層面上,建立Java物件通常僅僅是一個new關鍵字而已。

在虛擬遇到new指令時:

1、首先檢查這個指令的引數是否能在常量池中定位到一個類的符號引用,並檢查這個類的符號引用代表的類是否已經載入,解析和初始化過。如果沒有,則必須執行類載入過程

2、接下來為新生物件分配記憶體,物件所需的大小在類載入後便可以完全確定。

3、記憶體分配的方式有兩種-指標碰撞(Bump the Pointer)和空閒列表(Free List),具體選擇哪種分配方式是由Java堆是否規整決定的,而Java堆是否規整,則又是由所採用的垃圾收集器是否帶有壓縮整理功能決定的。

4、分配記憶體時需要考慮併發情況下執行緒的安全問題。有兩種方案可以避免,一種是對分配記憶體的動作進行同步處理——實際上虛擬機器採用CAS配上失敗重試的方式就可以保證更新操作的原子性;另外一種是把記憶體的分配動作按照執行緒劃分在不同的空間進行,也就是每個執行緒先分配一小塊記憶體,稱為本地執行緒緩衝(Thread Local Allocation Buffer,TLAB),哪個執行緒需要分配記憶體時,從TLAB上進行分配,當TLAB 用完時,才需要同步鎖定。

5、記憶體分配完成之後,虛擬機器需要將分配的記憶體空間初始化零值

6、接下來,虛擬機器要對物件進行必要的設定,例如這個物件是哪個類的例項,如何才能找到類的元資料資訊,物件的雜湊碼,GC分代年齡等,這些資訊放在物件頭(Object Header)中

當完成以上動作之後,從虛擬機器的視角來看,一個物件產生了,但是從Java程式的角度來看,物件的建立才剛剛開始——<init>方法還沒有執行。一般來說,執行完new指令後會接著執行<init>方法,把物件按照程式設計師的意願進行初始化。這樣一個真正可用的物件才算完全產生出來。

二、物件的記憶體佈局

在HotSpot虛擬機器中,物件在記憶體中的佈局可以分為3塊區域:物件頭(Header),例項資料(Instance Data),對齊填充(Padding)。

在HotSpot虛擬機器中,物件頭包含兩部分資訊,第一部分用於儲存物件自身的執行時資料,如雜湊碼值,GC分代年齡,鎖狀態標誌,執行緒持有的鎖,偏向執行緒ID,偏向時間戳。這部分資料長度在32位和64位虛擬機器中分別為32bit和64bit,官方稱它為Mark Word,Mark Word被設計成一個非固定資料結構,以便在極小的空間記憶體儲更多的資訊,物件頭的另一部分是型別指標,即物件指向它的類元資料的指標,虛擬機器通過這個指標來確定物件是哪個類的例項。並不是所有的虛擬機器實現都必須在物件資料上保留型別指標,換句話說,查詢物件並不一定要經過物件本身。如果Java物件是一個數組,那物件頭中還必須有一塊用於記錄陣列長度的資料。

例項資料是物件真正儲存的有效資訊,也是在程式程式碼中所定義的各種型別欄位內容

第三部分對齊填充並不是必然存在的,也沒有特別的含義,他僅僅起著佔位的作用。由於HotSpot虛擬自動記憶體管理系統要求物件的地址必須是8位元組的整數倍。當物件例項資料沒有對齊時,就需要通過對齊填充來補全。

三、物件的訪問定位

目前主流的訪問方式有使用控制代碼和直接指標兩種。

如果使用控制代碼訪問的話,那麼Java堆中將會劃分出一塊記憶體來作為控制代碼池,reference中儲存的就是物件的控制代碼地址,而控制代碼中包含了物件例項資料與型別資料各自的具體地址資訊

如果使用直接指標訪問,那麼Java堆中物件的佈局中就必須考慮如何放置訪問型別資料的相關資訊。而reference中儲存的直接就是物件的地址。(就HotSpot虛擬機器而言,使用的是這種方式進行訪問物件)