1. 程式人生 > >JVM二:物件的建立(詳解new後發生的故事)

JVM二:物件的建立(詳解new後發生的故事)

在Java中我們建立物件都會用new進行建立,下面我來接收一下new之後物件建立及記憶體分配的具體的過程

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

二:類載入檢查通過後,接下來虛擬機器為新生物件分配記憶體,因為物件所需記憶體的大小在類載入後是完全確定的(用引用,堆中存放例項),所以只需分配一個固定大小的記憶體即可。分配記憶體的方法用兩種(Java虛擬機器中)

①指標碰撞:假設Java堆中記憶體是絕對規整的,所有用過的記憶體都存放在一邊,空閒的記憶體存在在另一邊,中間放著一個以指標為分界點的指示器,分配記憶體就是把指標往空閒的那側移動物件需要的記憶體即可。但是如果Java記憶體堆不是完整的,就沒有辦法進行簡單的指標碰撞,

②空閒列表:空閒列表就是虛擬機器先對記憶體分塊,並用一個表記錄每個記憶體塊是否使用的情況,為物件分配記憶體時只需更新列表上的記錄即可,選擇哪一種分配方式取決於對中的記憶體使用情況是否完整。

但是在為物件分配記憶體的同時,需要考慮其它執行緒是否也在這一時刻需要分配,因此需要考慮到執行緒的安全問題,通過以下兩種方法進行解決:

①同步處理:一種是對分配記憶體的空間動作進行同步處理--實際上虛擬機器採用CAS配上失敗重試的方式保證更新操作的原子性

②本地執行緒分配快取TLAB:另一種是把記憶體分配的動作按照執行緒在不同的空間之中進行,及每個執行緒先預先分配一定的快取。

三:記憶體分配完成後,虛擬機器將分配到的記憶體空間都初始化為零值(不包括物件頭),如果採用的是TLAB的方法分配,則這一步驟在分配之前完成。

這一步操作保證了物件的例項欄位在Java程式碼中可以不付初始值就直接使用,程式訪問的值為false。這個解決了以前我在上Java課與老師爭論在執行構造方法是否會先初始化,明顯我對了,但是當時沒有拿出有說服力的證據。

四:在上面的步驟完成後,從虛擬機器的視角,一個新的物件已經產生了,但是從Java程式的視角來看,物件建立方法才剛剛開始,還要執行物件的init方法(構造方法),一個物件才完成建立

以下是HotSpot直譯器的程式碼片段(C++)

// 確保常量池中存放的是已解釋的類
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);
    }
  }
}