Java對象分配原理

本文基於openjdk11及hotspotjava

Java對象模型: OOP-Klass模型

在正式探討JVM對象的建立前,先簡單地介紹一下hotspot中實現的Java的對象模型。在JVM中,並無直接將Java對象映射成C++對象,而是採用了oop-klass模型,主要是不但願每一個對象中都包含有一份虛函數表,其中:數組

  1. OOP(Ordinary Object Point),表示對象的實例信息
  2. Klass,是Java類的在C++中的表示,用來描述Java類的信息

簡單地說,一個Java類在JVM中被拆分爲了兩個部分:數據和描述信息,分別對應OOP和Klass。bash

在具體的JVM源碼中,當加載一個Class時,會建立一個InstanceKlass對象,實例化的對象則對應InstanceOopDesc,其中InstanceKlass存放在元空間,InstanceOopDesc存放在堆中。數據結構

對象建立過程

首先先來看InstanceOopDesc的數據結構,InstanceOopDesc繼承了OopDesc,數據結構以下併發

// 此處爲了方便閱讀,改寫了一下代碼
class instanceOopDesc : public oopDesc {
 private:
  volatile markOop _mark;
  union _metadata {
    Klass*      _klass;
    narrowKlass _compressed_klass;
  } _metadata;
};
複製代碼

其中_metadata指向該對象的InstanceKlass,而_mark中則存儲了對象運行時的狀態數據,數據結構以下(圖中爲32位的狀況下的數據,64位也大同小異)oracle

32 bits:
--------
hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
size:32 ------------------------------------------>| (CMS free block)
PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
複製代碼

每一行都表明了一種狀況,描述了哈希碼、GC分代年齡、鎖等狀態信息,以下:jvm

hash: 哈希碼
age: 分代年齡
biased_lock: 偏向鎖標識位
lock: 鎖狀態標識位
JavaThread*: 持有偏向鎖的線程ID
epoch: 偏向時間戳
複製代碼

instanceOopDesc其實保存的是對象的頭部信息,除了頭部信息,對象還有數據,對象數據緊跟着頭部後面,圖示以下:ide

image-20190628015456829

1. 入口

image-20190628025924528

上圖截取了一段程序字節碼,紅線所框對應了Java中new操做的字節碼,Java中的new操做對應了字節碼的三個操做,本文主要講述第一個操做(new)。字節碼中new操做對應JVM中的InterpreterRuntime::_new,代碼以下,函數

// hotspot/share/interpreter/interpreterRuntime.cpp
IRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* thread, ConstantPool* pool, int index))
  Klass* k = pool->klass_at(index, CHECK);
  InstanceKlass* klass = InstanceKlass::cast(k);

  klass->check_valid_for_instantiation(true, CHECK); // 校驗:接口/抽象類/Class不能實例化
  klass->initialize(CHECK); // 初始化klass
  oop obj = klass->allocate_instance(CHECK); // 分配實例

  thread->set_vm_result(obj);
IRT_END
複製代碼

裏面主要包含了兩個部分:初始化klass和分配實例oop

2. 初始化klass

// hotspot/share/oops/instanceKlass.cpp
void InstanceKlass::initialize(TRAPS) {
  if (this->should_be_initialized()) {
    initialize_impl(CHECK);
  } else {
    assert(is_initialized(), "sanity check");
  }
}
複製代碼

在這裏咱們繼續看initialize_impl()方法

// hotspot/share/oops/instanceKlass.cpp
void InstanceKlass::initialize_impl(TRAPS) {
  HandleMark hm(THREAD);

  link_class(CHECK);     // 連接class

  bool wait = false;

  // Step 1
  {
    Handle h_init_lock(THREAD, init_lock());
    ObjectLocker ol(h_init_lock, THREAD, h_init_lock() != NULL);

    Thread *self = THREAD;

    // Step 2
    while(is_being_initialized() && !is_reentrant_initialization(self)) {
        wait = true;
      ol.waitUninterruptibly(CHECK);
    }

    // Step 3
    if (is_being_initialized() && is_reentrant_initialization(self)) {
      DTRACE_CLASSINIT_PROBE_WAIT(recursive, -1, wait);
      return;
    }

    // Step 4
    if (is_initialized()) {
      DTRACE_CLASSINIT_PROBE_WAIT(concurrent, -1, wait);
      return;
    }

    // Step 5
    if (is_in_error_state()) {
      DTRACE_CLASSINIT_PROBE_WAIT(erroneous, -1, wait);
      ResourceMark rm(THREAD);
      const char* desc = "Could not initialize class ";
      const char* className = external_name();
      size_t msglen = strlen(desc) + strlen(className) + 1;
      char* message = NEW_RESOURCE_ARRAY(char, msglen);
      if (NULL == message) {
        // Out of memory: can't create detailed error message
          THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), className);
      } else {
        jio_snprintf(message, msglen, "%s%s", desc, className);
          THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), message);
      }
    }

    // Step 6
    set_init_state(being_initialized);
    set_init_thread(self);
  }

  // Step 7
  if (!is_interface()) {
    Klass* super_klass = super();
    if (super_klass != NULL && super_klass->should_be_initialized()) {
      super_klass->initialize(THREAD);
    }
    if (!HAS_PENDING_EXCEPTION && has_nonstatic_concrete_methods()) {
      initialize_super_interfaces(THREAD);
    }

    if (HAS_PENDING_EXCEPTION) {
      Handle e(THREAD, PENDING_EXCEPTION);
      CLEAR_PENDING_EXCEPTION;
      {
        EXCEPTION_MARK;
        // Locks object, set state, and notify all waiting threads
        set_initialization_state_and_notify(initialization_error, THREAD);
        CLEAR_PENDING_EXCEPTION;
      }
      DTRACE_CLASSINIT_PROBE_WAIT(super__failed, -1, wait);
      THROW_OOP(e());
    }
  }

  AOTLoader::load_for_klass(this, THREAD);

  // Step 8
  {
    assert(THREAD->is_Java_thread(), "non-JavaThread in initialize_impl");
    JavaThread* jt = (JavaThread*)THREAD;
    DTRACE_CLASSINIT_PROBE_WAIT(clinit, -1, wait);
    PerfClassTraceTime timer(ClassLoader::perf_class_init_time(),
                             ClassLoader::perf_class_init_selftime(),
                             ClassLoader::perf_classes_inited(),
                             jt->get_thread_stat()->perf_recursion_counts_addr(),
                             jt->get_thread_stat()->perf_timers_addr(),
                             PerfClassTraceTime::CLASS_CLINIT);
    call_class_initializer(THREAD);
  }

  // Step 9
  if (!HAS_PENDING_EXCEPTION) {
    set_initialization_state_and_notify(fully_initialized, CHECK);
    {
      debug_only(vtable().verify(tty, true);)
    }
  }
  else {
    // Step 10 and 11
    Handle e(THREAD, PENDING_EXCEPTION);
    CLEAR_PENDING_EXCEPTION;
    JvmtiExport::clear_detected_exception((JavaThread*)THREAD);
    {
      EXCEPTION_MARK;
      set_initialization_state_and_notify(initialization_error, THREAD);
      CLEAR_PENDING_EXCEPTION;
      JvmtiExport::clear_detected_exception((JavaThread*)THREAD);
    }
    DTRACE_CLASSINIT_PROBE_WAIT(error, -1, wait);
    if (e->is_a(SystemDictionary::Error_klass())) {
      THROW_OOP(e());
    } else {
      JavaCallArguments args(e);
      THROW_ARG(vmSymbols::java_lang_ExceptionInInitializerError(),
                vmSymbols::throwable_void_signature(),
                &args);
    }
  }
  DTRACE_CLASSINIT_PROBE_WAIT(end, -1, wait);
}
複製代碼

2.1 連接

// hotspot/share/oops/instanceKlass.cpp
bool InstanceKlass::link_class_impl(bool throw_verifyerror, TRAPS) {
 
  if (is_linked()) {
    return true;
  }
  
  assert(THREAD->is_Java_thread(), "non-JavaThread in link_class_impl");
  JavaThread* jt = (JavaThread*)THREAD;

  // 先連接父類
  Klass* super_klass = super();
  if (super_klass != NULL) {
    if (super_klass->is_interface()) {
      ResourceMark rm(THREAD);
      Exceptions::fthrow(
        THREAD_AND_LOCATION,
        vmSymbols::java_lang_IncompatibleClassChangeError(),
        "class %s has interface %s as super class",
        external_name(),
        super_klass->external_name()
      );
      return false;
    }

    InstanceKlass* ik_super = InstanceKlass::cast(super_klass);
    ik_super->link_class_impl(throw_verifyerror, CHECK_false);
  }

  // 連接該類的全部藉口
  Array<Klass*>* interfaces = local_interfaces();
  int num_interfaces = interfaces->length();
  for (int index = 0; index < num_interfaces; index++) {
    InstanceKlass* interk = InstanceKlass::cast(interfaces->at(index));
    interk->link_class_impl(throw_verifyerror, CHECK_false);
  }

  if (is_linked()) {
    return true;
  }

  // 驗證 & 重寫
  {
    HandleMark hm(THREAD);
    Handle h_init_lock(THREAD, init_lock());
    ObjectLocker ol(h_init_lock, THREAD, h_init_lock() != NULL);

    if (!is_linked()) {
      if (!is_rewritten()) {
        {
          bool verify_ok = verify_code(throw_verifyerror, THREAD);
          if (!verify_ok) {
            return false;
          }
        }
        
        if (is_linked()) {
          return true;
        }

        // 重寫類
        rewrite_class(CHECK_false);
      } else if (is_shared()) {
        SystemDictionaryShared::check_verification_constraints(this, CHECK_false);
      }

      // 重寫完成後連接方法
      link_methods(CHECK_false);

      // 初始化vtable和itable
      ClassLoaderData * loader_data = class_loader_data();
      if (!(is_shared() &&
            loader_data->is_the_null_class_loader_data())) {
        ResourceMark rm(THREAD);
        vtable().initialize_vtable(true, CHECK_false);
        itable().initialize_itable(true, CHECK_false);
      }
      
      // 將類的狀態標記爲已連接
      set_init_state(linked);
      if (JvmtiExport::should_post_class_prepare()) {
        Thread *thread = THREAD;
        assert(thread->is_Java_thread(), "thread->is_Java_thread()");
        JvmtiExport::post_class_prepare((JavaThread *) thread, this);
      }
    }
  }
  return true;
}
複製代碼

class連接的過程就是這樣,主要步驟總結以下:

  1. 連接父類和實現的接口
  2. 重寫類
  3. 初始化vtable和itable
  4. 將類的狀態標記爲已連接

關於重寫類和初始化vtable、itable的內容有空新開一章,本文就不描述具體細節了。

2.2 初始化過程

這段初始化klass步驟在JVM規範中有詳細描述,假設當前類(接口)爲C,它持有一個獨有的初始化鎖LC

  1. 同步鎖LC,防止併發致使屢次初始化
  2. 若是有其餘線程正在初始化C,就釋放LC並阻塞當前線程直到那個線程完成初始化
  3. 若是是執行初始化的是當前線程,則代表是遞歸請求,釋放LC並正常完成初始化
  4. 若是C已經被初始化了,則釋放LC並正常完成初始化
  5. 若是C的對象處於一個錯誤狀態,則釋放LC並拋出NoClassDefFoundError異常
  6. 記錄C正在被當前線程初始化並釋放LC,初始化類中全部final static字段
  7. 若是C是一個類,初始化其父類和接口
  8. 判斷C是否打開斷言
  9. 執行類(接口)的初始化方法
  10. 標記C已經徹底初始化,並喚醒全部的等待線程
  11. 若是初始化失敗,則拋出異常,並將C標記爲錯誤,同時喚醒全部的等待線程

上文爲JVM11規範中的步驟,實際中能夠看到hotspot在實現時和規範所寫略有誤差,但基本差很少。

3. 分配實例

// hotspot/share/oops/instanceKlass.cpp
instanceOop InstanceKlass::allocate_instance(TRAPS) {
  bool has_finalizer_flag = has_finalizer(); // 是否存在非空finalize()方法
  int size = size_helper();  // 類的大小

  instanceOop i;

  i = (instanceOop)Universe::heap()->obj_allocate(this, size, CHECK_NULL);  // 分配對象
  if (has_finalizer_flag && !RegisterFinalizersAtInit) {
    i = register_finalizer(i, CHECK_NULL);
  }
  return i;
}
複製代碼

在這裏咱們比較關注的是堆空間分配對象環節,

3.1 堆空間分配對象

代碼以下:

// hotspot/share/gc/shared/memAllocator.cpp
oop MemAllocator::allocate() const {
  oop obj = NULL;
  {
    Allocation allocation(*this, &obj);
    HeapWord* mem = mem_allocate(allocation);
    if (mem != NULL) {
      obj = initialize(mem);
    }
  }
  return obj;
}
複製代碼

很容易能夠看到,此處的主流程分爲兩個部分,內存分配和初始化。

3.1.1 內存分配

直接打開代碼,以下:

// hotspot/share/gc/shared/memAllocator.cpp
HeapWord* MemAllocator::mem_allocate(Allocation& allocation) const {
  if (UseTLAB) {
    HeapWord* result = allocate_inside_tlab(allocation);
    if (result != NULL) {
      return result;
    }
  }

  return allocate_outside_tlab(allocation);
}
複製代碼

在這段代碼中,咱們能夠看到一個很耳熟的東西——TLAB(ThreadLocalAllocBuffer),默認狀況下TLAB是打開狀態,並且其對Java性能提高很是顯著。首先,先簡單介紹一下TLAB的概念,

由於JVM堆空間是全部線程共享的,所以分配一個對象時會鎖住整個堆,這樣效率就會比較低下。所以JVM在eden區分配了一塊空間做爲線程的私有緩衝區,這個緩衝區稱爲TLAB。不一樣線程不共享TLAB,所以在TLAB中分配對象時是無需上鎖的,從而能夠快速分配。

在這段代碼中,內存分配劃分爲了兩個部分——TLAB內分配和TLAB外分配。

a. TLAB內分配

咱們先來看看TLAB內分配的過程,

// hotspot/share/gc/shared/memAllocator.cpp
HeapWord* MemAllocator::allocate_inside_tlab(Allocation& allocation) const {
  HeapWord* mem = _thread->tlab().allocate(_word_size);
  if (mem != NULL) {
    return mem;
  }
  return allocate_inside_tlab_slow(allocation);
}
複製代碼

一樣的在TLAB的分配的過程當中,也被拆成了兩種狀況,一種是直接使用線程現有的TLAB來進行分配,代碼以下,在下面的這段代碼中,咱們能夠看到TLAB的分配就只是簡單地將top指針向上增長了size大小,而且將原先top的位置分配給了obj,所以分配效率能夠說是極速了。(事實上,TLAB就是經過start、top、end等指針標記了TLAB的存儲信息以及分配空間)

// hotspot/share/gc/shared/threadLocalAllocBuffer.inline.hpp
inline HeapWord* ThreadLocalAllocBuffer::allocate(size_t size) {
  invariants();     // 校驗TLAB是否合法
  HeapWord* obj = top();
  if (pointer_delta(end(), obj) >= size) {
    set_top(obj + size);
    invariants();
    return obj;
  }
  return NULL;
}
複製代碼

接下來咱們來看看TLAB內的慢分配,

// hotspot/share/gc/shared/memAllocator.cpp
HeapWord* MemAllocator::allocate_inside_tlab_slow(Allocation& allocation) const {
  HeapWord* mem = NULL;
  ThreadLocalAllocBuffer& tlab = _thread->tlab();

  if (ThreadHeapSampler::enabled()) {
    tlab.set_back_allocation_end();
    mem = tlab.allocate(_word_size);
    if (mem != NULL) {
      allocation._tlab_end_reset_for_sample = true;
      return mem;
    }
  }

  // 若是TLAB的剩餘空間大於閾值,則保留TLAB,這樣就會進入TLAB外分配。在這裏,每次TLAB分配失敗,該TLAB都會調大該閾值,以防線程重複分配一樣大小的對象
  if (tlab.free() > tlab.refill_waste_limit()) {
    tlab.record_slow_allocation(_word_size);
    return NULL;
  }

  // 計算一個新的TLAB的大小,公式=min{可用空間,期待空間+對象佔據空間,最大TLAB空間}
  size_t new_tlab_size = tlab.compute_size(_word_size);

  // 清理原先的TLAB。會將剩餘的未使用空間填充進一個假數組,創造EDEN連續的假象,而且將start、end、top等指針所有置爲空
  tlab.clear_before_allocation();

  if (new_tlab_size == 0) {
    return NULL;
  }

  // 建立一個新的TLAB,空間可能在min_tlab_size到new_tlab_size之間
  size_t min_tlab_size = ThreadLocalAllocBuffer::compute_min_size(_word_size);
  mem = _heap->allocate_new_tlab(min_tlab_size, new_tlab_size, &allocation._allocated_tlab_size);
  if (mem == NULL) {
    return NULL;
  }

  // 將分配的空間數據所有清0
  if (ZeroTLAB) {
    Copy::zero_to_words(mem, allocation._allocated_tlab_size);
  }

  // 將mem位置分配word_size大小給obj
  tlab.fill(mem, mem + _word_size, allocation._allocated_tlab_size);
  return mem;
}
複製代碼
b. TLAB外分配
// hotspot/share/gc/shared/memAllocator.cpp
HeapWord* MemAllocator::allocate_outside_tlab(Allocation& allocation) const {
  allocation._allocated_outside_tlab = true;
  HeapWord* mem = _heap->mem_allocate(_word_size, &allocation._overhead_limit_exceeded);
  if (mem == NULL) {
    return mem;
  }

  NOT_PRODUCT(_heap->check_for_non_bad_heap_word_value(mem, _word_size));
  size_t size_in_bytes = _word_size * HeapWordSize;
  _thread->incr_allocated_bytes(size_in_bytes);

  return mem;
}
複製代碼

這裏的核心關注點只有一個——堆內存分配,此處以openjdk11的默認GC——G1爲例,看一看分配的過程。

// hotspot/share/gc/g1/g1CollectedHeap.cpp
HeapWord* G1CollectedHeap::mem_allocate(size_t word_size,
                              					bool*  gc_overhead_limit_was_exceeded) {
  assert_heap_not_locked_and_not_at_safepoint();

  if (is_humongous(word_size)) {
    return attempt_allocation_humongous(word_size);
  }
  size_t dummy = 0;
  return attempt_allocation(word_size, word_size, &dummy);
}
複製代碼

在G1中,對象的分配分爲了兩種形式:大對象分配、普通分配。因爲代碼比較長,簡單描述大對象的分配過程以下:

  1. 檢查是否須要GC,如須要則觸發GC,由於大對象消耗堆的速度很是快
  2. 計算大對象須要佔據多少區塊,嘗試分配連續的空閒區塊
  3. 若是沒有足夠的連續空間,找到一塊包含空閒和使用中的連續區塊,嘗試擴展
  4. 嘗試GC,若是失敗達到閾值則分配失敗,進行下一步的普通分配

接下來的普通分配過程較爲複雜,本文就再也不深刻探究了。

3.1.2 初始化對象

代碼以下

// hotspot/share/gc/shared/memAllocator.cpp
oop ObjAllocator::initialize(HeapWord* mem) const {
  mem_clear(mem);
  return finish(mem);
}
複製代碼

其中mem_clear()方法比較簡單,就是將對象除頭部之外的數據所有置爲0,代碼以下,

// hotspot/share/gc/shared/memAllocator.cpp
void MemAllocator::mem_clear(HeapWord* mem) const {
  const size_t hs = oopDesc::header_size();
  oopDesc::set_klass_gap(mem, 0);
  Copy::fill_to_aligned_words(mem + hs, _word_size - hs);
}
複製代碼

接下來看看finish()函數,

// hotspot/share/gc/shared/memAllocator.cpp
oop MemAllocator::finish(HeapWord* mem) const {
  assert(mem != NULL, "NULL object pointer");
  if (UseBiasedLocking) {
    oopDesc::set_mark_raw(mem, _klass->prototype_header());
  } else {
    oopDesc::set_mark_raw(mem, markOopDesc::prototype());
  }
  oopDesc::release_set_klass(mem, _klass);
  return oop(mem);
}
複製代碼

還記得對象頭中有兩個屬性mark和metadata嗎?finish()方法就是設置對象的頭部數據。

3.2 註冊finalize()方法

因爲平時幾乎不多用到finalize(),且內部邏輯比較複雜,所以本文暫時不探究finalize的註冊機制。

4. 總體流程

整個JVM對象分配的總體流程大體以下,

image-20190628025235263

參考

[1] The Java® Virtual Machine Specification Java SE 11 Edition

相關文章
相關標籤/搜索