1. 程式人生 > >jvm物件的建立過程

jvm物件的建立過程

 

1虛擬機器中,物件的建立過程

討論的物件限於普通Java物件,不包括陣列和Class物件等。

虛擬機器遇到一條new指令時:過程如下

1 類載入

當虛擬機器遇到一條new指令時 ,首先去檢查這個指令的引數是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已經被載入、解析和初始化過,如果沒有,那必須先執行相應的類載入過程。 

2 記憶體分配

當已經執行過類載入過程後,會為新物件在Java堆中分配一塊大小已經確定(類載入後確定)的記憶體,具體的記憶體分配規則有兩種:

  1. 指標碰撞(Bump the Pointer)
    如果Java堆中的記憶體是絕對規整的,所有用過的記憶體放一邊,空閒的記憶體放到一邊,中間放著指標為分界點,分配記憶體就是把指標向空閒的一邊挪動一段與物件大小相等的距離。
  2. 空閒列表 (Free List )
    如果Java堆中的記憶體並不是規整對的,已使用的記憶體和空間相互交錯,虛擬機器會將可以用的記憶體維護到一個列表上,在分配記憶體時從這個列表中找到一塊足夠大的空間劃給物件。然後更新列表記錄(將對應的這個記錄在列表中刪去)。
  • 記憶體分配方式由Java堆是否規則來決定 --》Java堆中是否規整是根據虛擬機器所採用的垃圾收集器是否帶有壓縮整理功能決定的。
  • Serial、ParNew帶壓縮整理的收集器用指標碰撞。
  • CMS這種基於mark-sweep演算法的收集器通常用空閒列表。

3 防止併發

在虛擬機器上建立物件是非常頻繁的行為,所以要做到防止併發,有以下兩種方式可實現:

  1. 對分配記憶體空間的動作進行同步處理,實際上JVM採用CAS(Cmpare And Set)配上失敗重試的方式保證更新操作的原子性;
  2. 把記憶體分配的動作按照執行緒劃分在不同的空間之中進行,即為每個執行緒在java堆中預先分配一塊小記憶體,稱為本地執行緒分配緩衝區(Thread Local Allocation Buffer,TLAB)。分配記憶體時線上程的TLBA上分配,只有TLAB用完並分配新的TLAB時,才需要同步鎖定。JVM是否使用TLAB可以通過-XX:+UseTLAB引數來設定。

4 初始化物件記憶體空間(換個說法就是:為物件的所有欄位附預設值)

記憶體分配完成後,JVM將分配到的記憶體空間都初始化為零值(不包括物件頭),如果使用TLAB,這一過程可提前到TLAB分配時進行。

5 物件頭的設定

將物件的類、雜湊碼、物件的GC分代年齡等資訊設定到物件頭之中。

執行Java的init方法(換個說法就是:執行構造方法)

設定完物件頭後,從JVM的角度來看一個物件已經完成了,但是從java程式的角度來看還沒有建立完成呢。此時就需要執行init方法,呼叫構造方法,這樣一個真正可用的物件才算完全的產生出來。


下面程式碼是HotSpot虛擬機器bytecodeInterpreter.cpp中的程式碼片段(這個直譯器實現很少機會實際使用,大部分平臺上都使用模板 直譯器;當代碼通過JIT編譯器執行時差異就更大了。不過這段程式碼用於瞭解HotSpot的運作過程是沒有什麼問題的)。

// 確保常量池中存放的是已解釋的類
    if (!constants->tag_at(index).is_unresolved_klass()) {
      // 斷言確保是klassOop和instanceKlassOop(這部分下一節介紹)
      oop entry = (klassOop) *constants->obj_at_addr(index);
      assert(entry->is_klass(), "Should be resolved klass");
      klassOop k_entry = (klassOop) entry;
      assert(k_entry->klass_part()->oop_is_instance(), "Should be instanceKlass");
      instanceKlass* ik = (instanceKlass*) k_entry->klass_part();
      // 確保物件所屬型別已經經過初始化階段
      if ( ik->is_initialized() && ik->can_be_fastpath_allocated() ) {
        // 取物件長度
        size_t obj_size = ik->size_helper();
        oop result = NULL;
        // 記錄是否需要將物件所有欄位置零值
        bool need_zero = !ZeroTLAB;
        // 是否在TLAB中分配物件
        if (UseTLAB) {
          result = (oop) THREAD->tlab().allocate(obj_size);
        }
        if (result == NULL) {
          need_zero = true;
          // 直接在eden中分配物件
    retry:
          HeapWord* compare_to = *Universe::heap()->top_addr();
          HeapWord* new_top = compare_to + obj_size;
          // cmpxchg是x86中的CAS指令,這裡是一個C++方法,通過CAS方式分配空間,併發失敗的話,轉到retry中重試直至成功分配為止
          if (new_top <= *Universe::heap()->end_addr()) {
            if (Atomic::cmpxchg_ptr(new_top, Universe::heap()->top_addr(), compare_to) != compare_to) {
              goto retry;
            }
            result = (oop) compare_to;
          }
        }
        if (result != NULL) {
          // 如果需要,為物件初始化零值
          if (need_zero ) {
            HeapWord* to_zero = (HeapWord*) result + sizeof(oopDesc) / oopSize;
            obj_size -= sizeof(oopDesc) / oopSize;
            if (obj_size > 0 ) {
              memset(to_zero, 0, obj_size * HeapWordSize);
            }
          }
          // 根據是否啟用偏向鎖,設定物件頭資訊
          if (UseBiasedLocking) {
            result->set_mark(ik->prototype_header());
          } else {
            result->set_mark(markOopDesc::prototype());
          }
          result->set_klass_gap(0);
          result->set_klass(k_entry);
          // 將物件引用入棧,繼續執行下一條指令
          SET_STACK_OBJECT(result, 0);
          UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);
        }
      }
    }