1. 程式人生 > >Java虛擬機器(四)——物件的建立、儲存和定位

Java虛擬機器(四)——物件的建立、儲存和定位

物件的建立

  Java是一門面向物件的程式語言,Java 程式執行過程中無時無刻都有物件被創建出來,在語言層面上,建立物件(例如克隆,反序列化)通常僅僅是一個new關鍵字而已,例如下面的語句。

Object obj = new Object();

其實在在虛擬機器中,當遇到上述語句時,其執行過程大致要經歷下面幾個階段。

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

  2. 為新生物件分配記憶體
    在類載入檢查通過後,接下來虛擬機器將為新生物件分配記憶體。物件所需記憶體的大小在類載入完成後便可完全確定。為物件分配空間的任務等同於把一塊確定大小的記憶體從Java堆中劃分出來。

  3. 初始化記憶體空間
    記憶體分配完成後,虛擬機器需要將分配到的記憶體空間都初始化為零值(不包括物件頭),這一步操作保證了物件的例項欄位在Java程式碼中可以不賦初始值就直接使用,程式能訪問到這些欄位的資料型別所對應的零值。

  4. 對物件進行必要的設定
    接下來,虛擬機器要對物件進行必要的設定,例如這個物件是哪個類的例項、如何才能找到類的元資料資訊、物件的雜湊碼、物件的GC分代年齡等資訊。這些資訊存放在物件的物件頭(Object Header)之中。根據虛擬機器當前的執行狀態的不同,如是否啟用偏向鎖等,物件頭會有不同的設定方式。

物件的記憶體佈局

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

1. 物件頭

  物件頭包括兩部分資訊,第一部分用於儲存物件自身的執行時資料,如HashCode,GC 分代年齡,鎖狀態標誌,執行緒持有的鎖,偏向執行緒ID,偏向時間戳等。另外一部分是型別指標,即物件指向它的類元資料的指標,虛擬機器通過這個指標來確定這個物件是哪個類的例項。這部分資料的長度在32位和64位虛擬機器(未開啟壓縮指標)中分別為32bit和64bit。

2. 例項資料

  例項資料部分是物件真正儲存的有效資訊,也是在程式程式碼中所定義的各種型別的欄位內容。無論是從父類繼承下來的,還是在子類中定義的,都需要記錄起來。

3. 對齊填充

   對齊填充不是必須的,也沒有特別的含義,它僅僅起著佔位符的作用。之所以會出現對齊填充,是由於HotSpot VM 的自動記憶體管理系統要求物件起始地址必須是8位元組的整數倍,換句話說,就是物件的大小必須是8位元組的整數倍。當物件例項資料部分沒有對齊時,就需要通過對齊填充來補全。

物件的訪問定位

建立物件是為了使用物件。我們已經知道,物件的引用儲存在Java 虛擬機器棧中,而具體的物件實在堆中的。
由於reference型別在Java虛擬機器規範裡面只規定了一個指向物件的引用,並沒有定義這個引用應該通過哪種方式去定位,以及訪問到Java堆中的物件的具體位置,因此不同虛擬機器實現的物件訪問方式會有所不同,主流的訪問方式有兩種:使用控制代碼池和直接使用指標。

通過控制代碼池訪問的方式如下:

控制代碼池訪問

通過直接指標訪問的方式如下:

直接指標

這兩種物件的訪問方式各有優勢:

控制代碼方式:使用控制代碼訪問方式的最大好處就是reference中存放的是穩定的控制代碼地址,在物件被移動(垃圾收集時移動物件是非常普遍的行為)時只會改變控制代碼中的例項資料指標,而reference本身不需要修改,但是會多一次指標定位的開銷;

直接指標方式:使用直接指標訪問方式的最大好處是速度快,它節省了一次指標定位的時間開銷。

目前Java預設使用的HotSpot虛擬機器採用的便是第二種方式進行物件訪問的