Java虛擬機(二)對象的建立與OOP-Klass模型

相關文章
Java虛擬機系列算法

前言

在前一篇文章中咱們學習了Java虛擬機的結構原理與運行時數據區域,那麼咱們大概知道了Java虛擬機的內存的概況,那麼內存中的數據是如何建立和訪問的呢?這篇文章會給你答案。數組

1.對象的建立

對象的建立一般是經過new一個對象而已,當虛擬機接收到一個new指令時,它會作以下的操做。
(1)判斷對象對應的類是否加載、連接、初始化
虛擬機接收到一條new指令時,首先會去檢查這個指定的參數是否能在常量池中定位到一個類的符號引用,而且檢查這個符號引用表明的類是否已被類加載器加載、連接和初始化過。若是沒有則先執行相應的類加載過程。關於類加載器咱們在前一篇文章中已經提到過,這裏再也不贅述。安全

(2)爲對象分配內存
類加載完成後,接着會在Java堆中劃分一塊內存分配給對象。內存分配根據Java堆是否規整,有兩種方式:微信

  • 指針碰撞:若是Java堆的內存是規整,即全部用過的內存放在一邊,而空閒的的放在另外一邊。分配內存時將位於中間的指針指示器向空閒的內存移動一段與對象大小相等的距離,這樣便完成分配內存工做。
  • 空閒列表:若是Java堆的內存不是規整的,則須要由虛擬機維護一個列表來記錄那些內存是可用的,這樣在分配的時候能夠從列表中查詢到足夠大的內存分配給對象,並在分配後更新列表記錄。

Java堆的內存是否規整根據所採用的來及收集器是否帶有壓縮整理功能有關,關於垃圾收集器,本系列後面的文章會介紹。併發

(3)處理併發安全問題
建立對象是一個很是頻繁的操做,因此須要解決併發的問題,有兩種方式:app

  • 對分配內存空間的動做進行同步處理,好比在虛擬機採用CAS算法並配上失敗重試的方式保證更新操做的原子性。
  • 每一個線程在Java堆中預先分配一小塊內存,這塊內存稱爲本地線程分配緩衝(Thread Local Allocation Buffer)簡寫爲TLAB,線程須要分配內存時,就在對應線程的TLAB上分配內存,當線程中的TLAB用完而且被分配到了新的TLAB時,這時候才須要同步鎖定。經過-XX:+/-UserTLAB參數來設定虛擬機是否使用TLAB。

(4)初始化分配到的內存空間
將分配到的內存,除了對象頭都初始化爲零值。oop

(5)設置對象的對象頭
將對象的所屬類、對象的HashCode和對象的GC分代年齡等數據存儲在對象的對象頭中。源碼分析

(6)執行init方法進行初始化
執行init方法,初始化對象的成員變量、調用類的構造方法,這樣一個對象就被建立了出來。佈局

2.對象的堆內存佈局

對象建立完畢,而且已經在Java堆中分配了內存,那麼對象在堆內存是如何進行佈局的呢?
以HotSpot虛擬機爲例,對象在堆內存的佈局分爲三個區域,分別是對象頭(Header)、實例數據(Instance Data)、對齊填充(Padding)。學習

  • 對象頭:對象頭包括兩部分信息分別是Mark World和元數據指針,Mark World用於存儲對象運行時的數據,好比HashCode、鎖狀態標誌、GC分代年齡等。而元數據指針用於指向方法區的中目標類的類型信息,經過元數據指針能夠肯定對象的具體類型。
  • 實例數據:用於存儲對象中的各類類型的字段信息(包括從父類繼承來的)。
  • 對齊填充:對齊填充不必定存在,起到了佔位符的做用,沒有特別的含義。

對象的內存佈局以下圖所示。

堆內存佈局.png

3.HotSpot的對象模型

HotSpot中採用了OOP-Klass模型,它是用來描述Java對象實例的一種模型,OOP(Ordinary Object Pointer)指的是普通對象指針,而Klass用來描述對象實例的具體類型。
HotSpot中,用instanceOopDesc 和 arrayOopDesc 來描述對象頭,其中arrayOopDesc對象用於描述數組類型。
instanceOopDesc的代碼以下所示。
openjdk/hotspot/src/share/vm/oops/instanceOop.hpp

class instanceOopDesc : public oopDesc {
 public:
  // aligned header size.
  static int header_size() { return sizeof(instanceOopDesc)/HeapWordSize; }

  // If compressed, the offset of the fields of the instance may not be aligned.
  static int base_offset_in_bytes() {
    // offset computation code breaks if UseCompressedClassPointers
    // only is true
    return (UseCompressedOops && UseCompressedClassPointers) ?
             klass_gap_offset_in_bytes() :
             sizeof(instanceOopDesc);
  }

  static bool contains_field_offset(int offset, int nonstatic_field_size) {
    int base_in_bytes = base_offset_in_bytes();
    return (offset >= base_in_bytes &&
            (offset-base_in_bytes) < nonstatic_field_size * heapOopSize);
  }
};複製代碼

能夠看出instanceOopDesc繼承自oopDesc:
openjdk/hotspot/src/share/vm/oops/oop.hpp

class oopDesc {
  friend class VMStructs;
 private:
  volatile markOop  _mark;
  union _metadata {
    Klass*      _klass;
    narrowKlass _compressed_klass;
  } _metadata;

  // Fast access to barrier set. Must be initialized.
  static BarrierSet* _bs;
...
}複製代碼

oopDesc中包含兩個數據成員:_mark 和 _metadata。其中markOop類型的_mark對象指的是前面講到的Mark World。_metadata是一個共用體,其中_klass是普通指針,_compressed_klass是壓縮類指針,它們就是前面講到的元數據指針,這兩個指針都指向instanceKlass對象,它用來描述對象的具體類型。
instanceKlass的代碼以下所示。
openjdk/hotspot/src/share/vm/oops/instanceKlass.hpp

class InstanceKlass: public Klass {
  ...
  enum ClassState {
    allocated,                          // allocated (but not yet linked)
    loaded,                             // loaded and inserted in class hierarchy (but not linked yet)
    linked,                             // successfully linked/verified (but not initialized yet)
    being_initialized,                  // currently running class initializer
    fully_initialized,                  // initialized (successfull final state)
    initialization_error                // error happened during initialization
  };
  ...
 }複製代碼

instanceKlass繼承自Klass ,枚舉ClassState 用來標識對象的加載進度。
知道了OOP-Klass模型,咱們就能夠分析Java虛擬機是如何經過棧幀中的對象引用找到對應的對象實例,以下圖所示。

OOP-Klass模型(1).png

從圖中能夠看出,經過棧幀中的對象引用找到Java堆中的instanceOopDesc對象,再經過instanceOopDesc中的元數據指針來找到方法區中的instanceKlass,從而肯定該對象的具體類型。

參考資料
《深刻理解Java虛擬機》
《JAVA虛擬機精講》
深刻探究 JVM | klass-oop 對象模型研究
JVM源碼分析之Java對象的建立過程
JVM源碼分析之Java類的加載過程


歡迎關注個人微信公衆號,第一時間得到博客更新提醒,以及更多成體系的Android相關原創技術乾貨。
掃一掃下方二維碼或者長按識別二維碼,便可關注。

相關文章
相關標籤/搜索