JVM 源碼分析 - G1 對象分配全流程

前言

Java 程序中,我們經(jīng)常會創(chuàng)建各種各樣的對象實例,我們也都知道,絕大部分的對象實例都是在堆中進行分配的,但是 JVM 到底是如何在堆上為對象實例分配內(nèi)存空間的呢?下面筆者就以 OpenJDK 13G1 垃圾回收器為例,通過源碼來詳細了解一下分配的過程。

對象分配

我們以 new 關(guān)鍵字創(chuàng)建對象為例。

public class Test {
    public static void main(String[] args) {
        Test t = new Test();
    }
}
NEW 關(guān)鍵字

編譯過后,執(zhí)行 javap -v 命令可以看到 main() 方法對應(yīng)的字節(jié)碼。

public static void main(java.lang.String[]);
  descriptor: ([Ljava/lang/String;)V
  flags: (0x0009) ACC_PUBLIC, ACC_STATIC
  Code:
    stack=2, locals=2, args_size=1
       0: new           #7                  // class Test
       3: dup
       4: invokespecial #9                  // Method "<init>":()V
       7: astore_1
       8: return
    LineNumberTable:
      line 5: 0
      line 6: 8

new 指令對應(yīng)的處理邏輯在 src/share/vm/interpreter/bytecodeInterpretor.cpp 中,相關(guān)代碼如下:

CASE(_new): {
  u2 index = Bytes::get_Java_u2(pc+1);
  ConstantPool* constants = istate->method()->constants();
  if (!constants->tag_at(index).is_unresolved_klass()) {
    // Make sure klass is initialized and doesn't have a finalizer
    Klass* entry = constants->resolved_klass_at(index);
    InstanceKlass* ik = InstanceKlass::cast(entry);
    // 快速分配
    if (ik->is_initialized() && ik->can_be_fastpath_allocated() ) {
      ...
    }
  }
  // 慢速分配
  CALL_VM(InterpreterRuntime::_new(THREAD, METHOD->constants(), index),
          handle_exception);
  OrderAccess::storestore();
  SET_STACK_OBJECT(THREAD->vm_result(), 0);
  THREAD->set_vm_result(NULL);
  UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);
}

流程總的可以分為快速分配和慢速分配,快速分配是在滿足條件的情況下,通過 CAS 的方式嘗試在 TLAB 上進行分配(在慢速分配中會詳細介紹),如果失敗的話,則會執(zhí)行 InterpreterRuntime::_new() 方法進行慢速分配。

# src/hotspot/share/interpreter/interpreterRuntime.cpp

JRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* thread, ConstantPool* pool, int index))
  Klass* k = pool->klass_at(index, CHECK);
  InstanceKlass* klass = InstanceKlass::cast(k);

  // Make sure we are not instantiating an abstract klass
  klass->check_valid_for_instantiation(true, CHECK);

  // Make sure klass is initialized
  // 初始化
  klass->initialize(CHECK);

  // ...
  // 分配實例
  oop obj = klass->allocate_instance(CHECK);
  thread->set_vm_result(obj);
JRT_END

方法首先對類進行加載和初始化,重點看調(diào)用 klass->allocate_instance() 方法分配實例。

# src/hotspot/src/share/vm/oops/instanceKlass.cpp

instanceOop InstanceKlass::allocate_instance(TRAPS) {
  bool has_finalizer_flag = has_finalizer(); // Query before possible GC
  int size = size_helper();  // Query before forming handle.

  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;
}

方法首先通過 Universe::heap() 獲取到 CollectedHeap 的實例,該實例管理整個 JVM 的堆空間,管理的方式根據(jù)配置的不同垃圾回收器而有所不同,可以在該類的注釋上看到當(dāng)前 JDK 支持的垃圾回收器種類。

# src/hotspot/share/gc/shared/collectedHeap.hpp

// CollectedHeap
//   GenCollectedHeap (分代)
//     SerialHeap (Serial)
//     CMSHeap (CMS)
//   G1CollectedHeap (G1)
//   ParallelScavengeHeap (Parallel)
//   ShenandoahHeap (Shenandoah)
//   ZCollectedHeap (Z)
//   EpsilonHeap (Epsilon 補)
class CollectedHeap : public CHeapObj<mtInternal> {...}
CollectedHeap 對象分配

言歸正傳,繼續(xù)深入 CollectedHeap::obj_allocate() 方法。

# hotspot/src/share/vm/gc_interface/collectedHeap.inline.hpp

inline oop CollectedHeap::obj_allocate(Klass* klass, int size, TRAPS) {
  ObjAllocator allocator(klass, size, THREAD);
  return allocator.allocate();
}

ObjAllocator 繼承自 MemAllocator,且并沒有覆寫父類的 allocate() 方法,所以最后執(zhí)行的是 MemAllocator::allocate() 方法。

# src/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;
}

方法繼續(xù)調(diào)用 MemAllocator::mem_allocate() 方法,將對象分配分為在 TLAB 之上和之外兩種方式。

# src/hotspot/share/gc/shared/memAllocator.cpp

HeapWord* MemAllocator::mem_allocate(Allocation& allocation) const {
  // 開啟使用 TLAB,則嘗試在 TLAB 上分配
  if (UseTLAB) {
    HeapWord* result = allocate_inside_tlab(allocation);
    if (result != NULL) {
      return result;
    }
  }

  // 未開啟使用或上述分配失敗,則嘗試在 TLAB 之外分配
  return allocate_outside_tlab(allocation);
}
  1. 如果開啟使用 TLAB,則先嘗試在 TLAB 上分配,成功則直接返回。
  2. 如果未開啟使用 TLAB 或在 TLAB 上分配失敗,則嘗試在 TLAB 之外分配,也就是在 Region 中直接分配。

簡單介紹一下 TLAB

TLAB 全稱 ThreadLocalAllocBuffer,是線程的一塊私有內(nèi)存,如果設(shè)置了虛擬機參數(shù) -XX:UseTLAB,在線程初始化時,同時也會分配一塊指定大小的內(nèi)存,只給當(dāng)前線程使用,這樣每個線程都單獨擁有一個 Buffer,如果需要分配內(nèi)存,就在自己的 Buffer 上分配,這樣就不存在競爭的情況,可以大大提升分配效率,當(dāng) Buffer 容量不夠的時候,再重新從 Eden 區(qū)域分配一塊繼續(xù)使用,這個分配動作還是需要原子操作的。

更多參考:JVM源碼分析之線程局部緩存TLAB

TLAB 上分配

先看一下在 TLAB 之上分配的 allocate_inside_tlab() 方法,在具體分配上又分為兩種,快速分配和慢速分配。

# src/hotspot/share/gc/shared/memAllocator.cpp

HeapWord* MemAllocator::allocate_inside_tlab(Allocation& allocation) const {
  // Try allocating from an existing TLAB.
  // 快速分配
  HeapWord* mem = _thread->tlab().allocate(_word_size);
  if (mem != NULL) {
    return mem;
  }

  // Try refilling the TLAB and allocating the object in it.
  // 慢速分配
  return allocate_inside_tlab_slow(allocation);
}

1. TLAB 上分配 - 快速分配

快速分配很簡單,即在當(dāng)前線程所擁有的 TLAB 上嘗試分配,如果空間足夠,則分配成功,否則,分配失敗。

# src/hotspot/share/gc/shared/threadLocalAllocBuffer.inline.hpp

inline HeapWord* ThreadLocalAllocBuffer::allocate(size_t size) {
  invariants();
  HeapWord* obj = top();
  if (pointer_delta(end(), obj) >= size) {
    // successful thread-local allocation
#ifdef ASSERT
    // Skip mangling the space corresponding to the object header to
    // ensure that the returned space is not considered parsable by
    // any concurrent GC thread.
    size_t hdr_size = oopDesc::header_size();
    Copy::fill_to_words(obj + hdr_size, size - hdr_size, badHeapWordVal);
#endif // ASSERT
    // This addition is safe because we know that top is
    // at least size below end, so the add can't wrap.
    set_top(obj + size);

    invariants();
    return obj;
  }
  return NULL;
}

2. TLAB 上分配 - 慢速分配

慢速分配是在快速分配失敗之后的方案,也就當(dāng)前線程所擁有的 TLAB 上空間不足,所以慢速分配的核心就是分配新的 TLAB,然后再在其上進行對象分配。下面是慢速分配的完整代碼:

# src/hotspot/share/gc/shared/memAllocator.cpp

HeapWord* MemAllocator::allocate_inside_tlab_slow(Allocation& allocation) const {
  HeapWord* mem = NULL;
  // 獲取當(dāng)前線程對應(yīng)的 TLAB
  ThreadLocalAllocBuffer& tlab = _thread->tlab();

  // 步驟一
  // 如果當(dāng)前 TLAB 中存在采樣對象,則在刪除后再次嘗試快速分配
  if (JvmtiExport::should_post_sampled_object_alloc()) {
    tlab.set_back_allocation_end();
    mem = tlab.allocate(_word_size);

    // We set back the allocation sample point to try to allocate this, reset it
    // when done.
    allocation._tlab_end_reset_for_sample = true;

    if (mem != NULL) {
      return mem;
    }
  }

  // Retain tlab and allocate object in shared space if
  // the amount free in the tlab is too large to discard.
  // 步驟二
  // 如果當(dāng)前 TLAB 的剩余容量大于浪費的限制值,則記錄這次的慢分配,并且會向上微調(diào)限制值。
  if (tlab.free() > tlab.refill_waste_limit()) {
    tlab.record_slow_allocation(_word_size);
    return NULL;
  }

  // Discard tlab and allocate a new one.
  // To minimize fragmentation, the last TLAB may be smaller than the rest.
  // 計算新的 TLAB 的大小
  size_t new_tlab_size = tlab.compute_size(_word_size);

  // 廢棄當(dāng)前線程擁有的 TLAB
  tlab.retire_before_allocation();

  // 如果新的 TLAB 的大小為零,說明當(dāng)前沒有足夠大的空間。
  if (new_tlab_size == 0) {
    return NULL;
  }

  // Allocate a new TLAB requesting new_tlab_size. Any size
  // between minimal and new_tlab_size is accepted.
  // 步驟三
  // 分配一個大小在 min_tlab_size 和 new_tlab_size 之間的 TLAB。
  size_t min_tlab_size = ThreadLocalAllocBuffer::compute_min_size(_word_size);
  mem = Universe::heap()->allocate_new_tlab(min_tlab_size, new_tlab_size, &allocation._allocated_tlab_size);
  // 分配新的 TLAB 失敗
  if (mem == NULL) {
    ...
    return NULL;
  }
  ...

  // 步驟四
  if (ZeroTLAB) {
    // ..and clear it.
    // 將整個 TLAB 空間清零
    Copy::zero_to_words(mem, allocation._allocated_tlab_size);
  } else {
    // ...and zap just allocated object.
#ifdef ASSERT
    // Skip mangling the space corresponding to the object header to
    // ensure that the returned space is not considered parsable by
    // any concurrent GC thread.
    // 將 TLAB 開始的對象頭大小的空間寫入“壞值”,這樣任何并發(fā)的 GC 線程都不會將其解析為對象。
    // 就是犧牲較小的空間,來避免 GC 線程可能的誤處理。
    size_t hdr_size = oopDesc::header_size();
    Copy::fill_to_words(mem + hdr_size, allocation._allocated_tlab_size - hdr_size, badHeapWordVal);
#endif // ASSERT
  }

  // 以首次填充的方式初始化 TLAB
  tlab.fill(mem, mem + _word_size, allocation._allocated_tlab_size);
  return mem;
}

為了方便說明,這里將慢速分配的整個過程分為四個步驟,具體的步驟劃分和大致的說明可以參看上面代碼中的注釋,下面我們將按照這四個步驟分別來分析。

2.1. TLAB 慢速分配 - 步驟一
# src/hotspot/share/gc/shared/memAllocator.cpp - allocate_inside_tlab_slow()

// 步驟一
// 如果當(dāng)前 TLAB 中存在采樣對象,則在刪除后再次嘗試快速分配
if (JvmtiExport::should_post_sampled_object_alloc()) {
  tlab.set_back_allocation_end();
  mem = tlab.allocate(_word_size);

  // We set back the allocation sample point to try to allocate this, reset it
  // when done.
  allocation._tlab_end_reset_for_sample = true;

  if (mem != NULL) {
    return mem;
  }
}

由于對采樣對象分配這塊不甚了解,所以只能通過代碼猜測大致的邏輯,tlab.set_back_allocation_end() 方法的作用是將 TLAB_allocation_end 字段值賦值給 _end 字段,具體的說明可以看一下源碼中的注釋:

# src/hotspot/share/gc/shared/threadLocalAllocBuffer.hpp

class ThreadLocalAllocBuffer: public CHeapObj<mtThread> {
private:
  HeapWord* _start;                 // address of TLAB
  HeapWord* _top;                   // address after last allocation
  HeapWord* _pf_top;                // allocation prefetch watermark
  HeapWord* _end;                   // allocation end (can be the sampling end point or _allocation_end)
                                    // 分配結(jié)尾(可以是采樣終點,或者 _allocation_end)
  HeapWord* _allocation_end;        // end for allocations (actual TLAB end, excluding alignment_reserve)
                                    // 實際結(jié)尾(TLAB 真實結(jié)尾)
...
}

從注釋可以得知,_end 位置是小于 _allocation_end 的,兩者之間的空間應(yīng)該就是用于存放采樣對象的,tlab.set_back_allocation_end() 方法就是釋放這塊的空間,然后調(diào)用 tlab.allocate() 方法,嘗試再次在 TLAB 上快速分配。

2.2. TLAB 慢速分配 - 步驟二
# src/hotspot/share/gc/shared/memAllocator.cpp - allocate_inside_tlab_slow()

// Retain tlab and allocate object in shared space if
// the amount free in the tlab is too large to discard.
// 步驟二
// 如果當(dāng)前 TLAB 的剩余空間超過允許浪費的閾值,則記錄這次的慢分配,并且會向上微調(diào)閾值。
if (tlab.free() > tlab.refill_waste_limit()) {
  tlab.record_slow_allocation(_word_size);
  return NULL;
}

// Discard tlab and allocate a new one.
// To minimize fragmentation, the last TLAB may be smaller than the rest.
// 計算新的 TLAB 的大小
size_t new_tlab_size = tlab.compute_size(_word_size);

// 廢棄當(dāng)前線程擁有的 TLAB
tlab.retire_before_allocation();

// 如果新的 TLAB 的大小為零,說明當(dāng)前沒有足夠大的空間。
if (new_tlab_size == 0) {
  return NULL;
}

該步驟首先會先判斷當(dāng)前 TLAB 中剩余的空間是否超過允許浪費的閾值,超過的話,說明剩余空間還比較多,不應(yīng)該浪費,那此時會記錄這次慢分配,同時會向上微調(diào)該閾值,之后對象會嘗試在 TLAB 之外進行分配;如果不超過的話,說明剩余空間很少,可以廢棄當(dāng)前線程擁有的 TLAB,然后重新分配新的 TLAB(步驟三)。

順便說一下,該閾值可以使用 TLABRefillWasteFraction 來調(diào)整,它表示 TLAB 中可以接受的浪費空間的比例,默認值為 64,即表示當(dāng) TLAB 中剩余空間不足整個的 1/64 時,可以廢棄當(dāng)前 TLAB,創(chuàng)建新的使用,否則要盡量使用當(dāng)前的 TLAB

2.3. TLAB 慢速分配 - 步驟三
# src/hotspot/share/gc/shared/memAllocator.cpp - allocate_inside_tlab_slow()

// Allocate a new TLAB requesting new_tlab_size. Any size
// between minimal and new_tlab_size is accepted.
// 步驟三
// 分配一個大小在 min_tlab_size 和 new_tlab_size 之間的 TLAB。
size_t min_tlab_size = ThreadLocalAllocBuffer::compute_min_size(_word_size);
mem = Universe::heap()->allocate_new_tlab(min_tlab_size, new_tlab_size, &allocation._allocated_tlab_size);
// 分配新的 TLAB 失敗
if (mem == NULL) {
  ...
  return NULL;
}

核心是通過 Universe::heap()->allocate_new_tlab() 方法分配一個新的,且大小在可接受范圍的 TLAB ,由于本文是選擇 G1 作為垃圾回收器,所以我們來看看該方法在 G1 中的實現(xiàn):

# hotspot/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp

HeapWord* G1CollectedHeap::allocate_new_tlab(size_t min_size,
                                             size_t requested_size,
                                             size_t* actual_size) {
  return attempt_allocation(min_size, requested_size, actual_size);
}

繼續(xù)深入看一下 attempt_allocation() 方法。

# hotspot/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp

inline HeapWord* G1CollectedHeap::attempt_allocation(size_t min_word_size,
                                                     size_t desired_word_size,
                                                     size_t* actual_word_size) {
  // 嘗試在當(dāng)前 Region 中分配
  HeapWord* result = _allocator->attempt_allocation(min_word_size, desired_word_size, actual_word_size);

  if (result == NULL) {
    // 慢分配(需要分配新的 Region)
    *actual_word_size = desired_word_size;
    result = attempt_allocation_slow(desired_word_size);
  }

  assert_heap_not_locked();
  if (result != NULL) {
    // 分配成功
    dirty_young_block(result, *actual_word_size);
  } else {
    // 分配失敗
    *actual_word_size = 0;
  }

  return result;
}

可以看出,在分配新的內(nèi)存空間時,同樣包括 _allocator->attempt_allocation() 快速分配和 attempt_allocation_slow() 慢速分配,快速分配是在已有的 Region 中直接分配新的 TLAB,慢速分配則需要先分配新的 Region,然后再在新的 Region 中分配 TLAB,我們分別來看一下這兩種分配。

2.3.1. 步驟三 - G1 快速分配 TLAB
# hotspot/src/share/vm/gc_implementation/g1/g1AllocRegion.inline.hpp

inline HeapWord* G1Allocator::attempt_allocation(size_t min_word_size,
                                                 size_t desired_word_size,
                                                 size_t* actual_word_size) {
  // 嘗試在保留分區(qū)中分配
  HeapWord* result = mutator_alloc_region()->attempt_retained_allocation(min_word_size, desired_word_size, actual_word_size);
  if (result != NULL) {
    return result;
  }
  // 嘗試在當(dāng)前分區(qū)中分配
  return mutator_alloc_region()->attempt_allocation(min_word_size, desired_word_size, actual_word_size);
}

快速分配分為兩種情況,一是通過 mutator_alloc_region()->attempt_retained_allocation() 嘗試在保留 Region 中分配,二是使用 mutator_alloc_region()->attempt_allocation() 嘗試在當(dāng)前使用的 Region 上分配。由于這兩個方法高度相似,所以放到一起分析。

# src/hotspot/share/gc/g1/g1AllocRegion.inline.hpp

inline HeapWord* MutatorAllocRegion::attempt_retained_allocation(size_t min_word_size,
                                                                 size_t desired_word_size,
                                                                 size_t* actual_word_size) {
  // 判斷保留分區(qū)是否存在
  if (_retained_alloc_region != NULL) {
    // 嘗試在保留分區(qū)上并發(fā)分配
    HeapWord* result = par_allocate(_retained_alloc_region, min_word_size, desired_word_size, actual_word_size);
    if (result != NULL) {
      return result;
    }
  }
  return NULL;
}


inline HeapWord* G1AllocRegion::attempt_allocation(size_t min_word_size,
                                                   size_t desired_word_size,
                                                   size_t* actual_word_size) {
  HeapRegion* alloc_region = _alloc_region;

  // 嘗試在當(dāng)前分區(qū)上并發(fā)分配
  HeapWord* result = par_allocate(alloc_region, min_word_size, desired_word_size, actual_word_size);
  if (result != NULL) {
    return result;
  }
  return NULL;
}

介紹一下保留分區(qū) _retained_alloc_region,之前有說到當(dāng) TLAB 中剩余空間小于浪費閾值時,可以廢棄當(dāng)前的 TLAB,否則需要繼續(xù)使用。同樣 Region 分配和使用也有類似的規(guī)則,不同的是,如果將要廢棄的 Region 中剩余的空間仍然可以容納一個最小的 TLAB 時,則在使用新的 Region 的同時,記錄該 Region,也就是保留分區(qū),這樣之后分配新的 TLAB 時可能會用上,這樣可以在一定程度上減少整體空間的浪費。

雖然兩種分配所在的 Region 有所不同,但最終都是調(diào)用 par_allocate() 方法來實現(xiàn) TLABRegion 中的分配的,我們來具體看一下。

# src/hotspot/share/gc/g1/g1AllocRegion.inline.hpp

inline HeapWord* G1AllocRegion::par_allocate(HeapRegion* alloc_region,
                                             size_t min_word_size,
                                             size_t desired_word_size,
                                             size_t* actual_word_size) {
  if (!_bot_updates) {
    // 年輕代
    return alloc_region->par_allocate_no_bot_updates(min_word_size, desired_word_size, actual_word_size);
  } else {
    // 老年代
    return alloc_region->par_allocate(min_word_size, desired_word_size, actual_word_size);
  }
}

方法有一個核心的邏輯字段 _bot_updates,其中 bot 表示 BlockOffsetTable,該字段值是在創(chuàng)建分區(qū)分配器的時候就已經(jīng)確定,如下:

# src/hotspot/share/gc/g1/g1AllocRegion.hpp

// Eden 區(qū)分配器
class MutatorAllocRegion : public G1AllocRegion {
...
public:
  MutatorAllocRegion()
    : G1AllocRegion("Mutator Alloc Region", false /* bot_updates */),
      _wasted_bytes(0),
      _retained_alloc_region(NULL) { }
...
};

// Survivor 區(qū)分配器
class SurvivorGCAllocRegion : public G1GCAllocRegion {
public:
  SurvivorGCAllocRegion(G1EvacStats* stats)
  : G1GCAllocRegion("Survivor GC Alloc Region", false /* bot_updates */, stats, G1HeapRegionAttr::Young) { }
};

// Old 區(qū)分配器
class OldGCAllocRegion : public G1GCAllocRegion {
public:
  OldGCAllocRegion(G1EvacStats* stats)
  : G1GCAllocRegion("Old GC Alloc Region", true /* bot_updates */, stats, G1HeapRegionAttr::Old) { }
  ...
};

由于我們是通過 new 關(guān)鍵字來創(chuàng)建對象實例,所以使用的是 Eden 區(qū)的分配器,那么 _bot_updates 值就為 false。需要注意的是,如果實例是大對象的話,是不是就應(yīng)該在 Old 區(qū)中分配?邏輯確實是這樣的,但是大對象的分配并非在這里,而是在開頭介紹的 MemAllocator::allocate_outside_tlab() 方法中實現(xiàn),也就是在 TLAB 之外分配的。

所以當(dāng)前會執(zhí)行 alloc_region->par_allocate_no_bot_updates() 方法,該方法會再調(diào)用 par_allocate_impl() 方法,然后通過不斷的使用 CAS 去競爭分配空間,直到當(dāng)前 Region 中沒有足夠需要的空間為止。

# src/hotspot/share/gc/g1/heapRegion.inline.hpp

inline HeapWord* HeapRegion::par_allocate_no_bot_updates(size_t min_word_size,
                                                         size_t desired_word_size,
                                                         size_t* actual_word_size) {
  return par_allocate_impl(min_word_size, desired_word_size, actual_word_size);
}


inline HeapWord* G1ContiguousSpace::par_allocate_impl(size_t min_word_size,
                                                      size_t desired_word_size,
                                                      size_t* actual_size) {
  do {
    HeapWord* obj = top();
    // 獲取當(dāng)前 Region 中可用空間
    size_t available = pointer_delta(end(), obj);
    size_t want_to_allocate = MIN2(available, desired_word_size);
    if (want_to_allocate >= min_word_size) {
      HeapWord* new_top = obj + want_to_allocate;
      // 通過不斷 CAS 方式來分配空間,直到當(dāng)前 Region 中沒有足夠需要的空間為止。
      HeapWord* result = Atomic::cmpxchg(new_top, top_addr(), obj);
      // result can be one of two:
      //  the old top value: the exchange succeeded
      //  otherwise: the new value of the top is returned.
      if (result == obj) {
        *actual_size = want_to_allocate;
        return obj;
      }
    } else {
      return NULL;
    }
  } while (true);
}
2.3.2. 步驟三 - G1 慢速分配 TLAB

如果快速分配 TLAB 失敗,那么流程將進入 G1CollectedHeap::attempt_allocation_slow() 慢速分配 TLAB 方法中。該方法有點復(fù)雜,為了說明方便,同樣將方法分為三個步驟,之后會依次進行分析。

先從整體上看一下,方法就是由一個大的循環(huán)體構(gòu)成,在循環(huán)體中將會不斷嘗試為當(dāng)前線程分配 TLAB 的空間,直到分配成功或者到達最大嘗試次數(shù)。

# src/hotspot/share/gc/g1/g1CollectedHeap.cpp

HeapWord* G1CollectedHeap::attempt_allocation_slow(size_t word_size) {
  ResourceMark rm; // For retrieving the thread names in log messages.

  // We should only get here after the first-level allocation attempt
  // (attempt_allocation()) failed to allocate.

  // We will loop until a) we manage to successfully perform the
  // allocation or b) we successfully schedule a collection which
  // fails to perform the allocation. b) is the only case when we'll
  // return NULL.
  HeapWord* result = NULL;
  for (uint try_count = 1, gclocker_retry_count = 0; /* we'll return */; try_count += 1) {
    bool should_try_gc;
    uint gc_count_before;

    // 步驟一
    {
      // 堆加鎖,并嘗試分配
      MutexLocker x(Heap_lock);
      result = _allocator->attempt_allocation_locked(word_size);
      if (result != NULL) {
        return result;
      }

      // If the GCLocker is active and we are bound for a GC, try expanding young gen.
      // This is different to when only GCLocker::needs_gc() is set: try to avoid
      // waiting because the GCLocker is active to not wait too long.
      // 如果 GCLocker 處于活動狀態(tài)并且必須要進行 GC,說明很快將會執(zhí)行一次 GC,
      // 再加上 Young Region 還有擴展的可能,就可以強制分配一個新的 Region。
      if (GCLocker::is_active_and_needs_gc() && policy()->can_expand_young_list()) {
        // No need for an ergo message here, can_expand_young_list() does this when
        // it returns true.
        result = _allocator->attempt_allocation_force(word_size);
        if (result != NULL) {
          return result;
        }
      }
      // Only try a GC if the GCLocker does not signal the need for a GC. Wait until
      // the GCLocker initiated GC has been performed and then retry. This includes
      // the case when the GC Locker is not active but has not been performed.
      // 僅當(dāng) GCLocker 狀態(tài)為不需要使用 GC 時,才嘗試進行 GC。
      should_try_gc = !GCLocker::needs_gc();
      // Read the GC count while still holding the Heap_lock.
      gc_count_before = total_collections();
    }

    // 步驟二
    if (should_try_gc) {
      bool succeeded;
      // 執(zhí)行 GC,并返回分配的地址
      result = do_collection_pause(word_size, gc_count_before, &succeeded,
                                   GCCause::_g1_inc_collection_pause);
      if (result != NULL) {
        return result;
      }

      // 成功執(zhí)行 GC 之后,還是分配失敗,則返回 NULL。
      if (succeeded) {
        // We successfully scheduled a collection which failed to allocate. No
        // point in trying to allocate further. We'll just return NULL.
        return NULL;
      }
    } else {
      // Failed to schedule a collection.
      // 各種嘗試都失敗后,判斷嘗試的次數(shù)是否到達最大嘗試次數(shù),到達則返回 NULL。
      if (gclocker_retry_count > GCLockerRetryAllocationCount) {
        return NULL;
      }
      // The GCLocker is either active or the GCLocker initiated
      // GC has not yet been performed. Stall until it is and
      // then retry the allocation.
      // 線程自旋等待 GCLocker 需要 GC 的標(biāo)志位被清除,這樣可以在下次循環(huán)中再次嘗試 GC。
      GCLocker::stall_until_clear();
      gclocker_retry_count += 1;
    }

    // We can reach here if we were unsuccessful in scheduling a
    // collection (because another thread beat us to it) or if we were
    // stalled due to the GC locker. In either can we should retry the
    // allocation attempt in case another thread successfully
    // performed a collection and reclaimed enough space. We do the
    // first attempt (without holding the Heap_lock) here and the
    // follow-on attempt will be at the start of the next loop
    // iteration (after taking the Heap_lock).
    // 到達這里,說明在執(zhí)行 GC 后分配失敗,或者線程自旋等待結(jié)束,不管是哪種情況,
    // 考慮到其它線程可能已經(jīng)執(zhí)行完 GC 并獲得足夠的空間,我們都需要再次嘗試分配,
    // 這次的嘗試是不對堆加鎖的(更快),如果失敗的話,下次循環(huán)開始時會進行加鎖分配。
    size_t dummy = 0;
    result = _allocator->attempt_allocation(word_size, word_size, &dummy);
    if (result != NULL) {
      return result;
    }
  }

  ShouldNotReachHere();
  return NULL;
}
2.3.2.1. G1 慢速分配 TLAB - 步驟一
# src/hotspot/share/gc/g1/g1CollectedHeap.cpp - attempt_allocation_slow()

// 步驟一
{
  // 堆加鎖,并嘗試分配
  MutexLocker x(Heap_lock);
  result = _allocator->attempt_allocation_locked(word_size);
  if (result != NULL) {
    return result;
  }

  // If the GCLocker is active and we are bound for a GC, try expanding young gen.
  // This is different to when only GCLocker::needs_gc() is set: try to avoid
  // waiting because the GCLocker is active to not wait too long.
  // 如果 GCLocker 處于活動狀態(tài)并且必須要進行 GC,說明很快將會執(zhí)行一次 GC,
  // 再加上 Young Region 還有擴展的可能,就可以強制分配一個新的 Region。
  if (GCLocker::is_active_and_needs_gc() && policy()->can_expand_young_list()) {
    result = _allocator->attempt_allocation_force(word_size);
    if (result != NULL) {
      return result;
    }
  }
  // Only try a GC if the GCLocker does not signal the need for a GC. Wait until
  // the GCLocker initiated GC has been performed and then retry. This includes
  // the case when the GC Locker is not active but has not been performed.
  // 僅當(dāng) GCLocker 狀態(tài)為不需要使用 GC 時,才嘗試進行 GC。
  should_try_gc = !GCLocker::needs_gc();
  // Read the GC count while still holding the Heap_lock.
  gc_count_before = total_collections();
}

由于之前通過 CAS 方式的快速分配的失敗,可能是因為并發(fā)分配競爭太過激烈,所以慢速分配方法首先就嘗試對整個堆分區(qū)進行加鎖,然后再調(diào)用 _allocator->attempt_allocation_locked() 方法進行分配。

# src/hotspot/share/gc/g1/g1Allocator.inline.hpp

inline HeapWord* G1Allocator::attempt_allocation_locked(size_t word_size) {
  HeapWord* result = mutator_alloc_region()->attempt_allocation_locked(word_size);
  return result;
}

繼續(xù)看 mutator_alloc_region()->attempt_allocation_locked() 方法,該方法又繼續(xù)調(diào)用更多參數(shù)的 attempt_allocation_locked() 方法。

# src/hotspot/share/gc/g1/g1AllocRegion.inline.hpp

inline HeapWord* G1AllocRegion::attempt_allocation_locked(size_t word_size) {
  size_t temp;
  return attempt_allocation_locked(word_size, word_size, &temp);
}


inline HeapWord* G1AllocRegion::attempt_allocation_locked(size_t min_word_size,
                                                          size_t desired_word_size,
                                                          size_t* actual_word_size) {
  // First we have to redo the allocation, assuming we're holding the
  // appropriate lock, in case another thread changed the region while
  // we were waiting to get the lock.
  // 首先再次嘗試在當(dāng)前 Region 中分配內(nèi)存,主要是考慮當(dāng)前線程在等待獲取堆鎖的時候,
  // 可能有其它線程改變了當(dāng)前的 Region(比如使用新的 Region)。
  HeapWord* result = attempt_allocation(min_word_size, desired_word_size, actual_word_size);
  if (result != NULL) {
    return result;
  }

  // 廢棄當(dāng)前的 Region
  retire(true /* fill_up */);
  // 獲取一個新的 Region,并在其中分配。
  result = new_alloc_region_and_allocate(desired_word_size, false /* force */);
  if (result != NULL) {
    *actual_word_size = desired_word_size;
    return result;
  }
  return NULL;
}

考慮當(dāng)前線程在等待獲取堆鎖的時候,其它線程有可能改變了當(dāng)前的 Region,比如使用了新的 Region。所以方法首先再次嘗試調(diào)用 attempt_allocation() 方法在當(dāng)前 Region 中分配 TLAB,該方法之前已經(jīng)有分析了,這里就不在贅述。我們主要看一下在分配失敗后,如何在 new_alloc_region_and_allocate() 方法中獲取新 Region 并進行 TLAB 分配的。

# src/hotspot/share/gc/g1/g1AllocRegion.cpp

HeapWord* G1AllocRegion::new_alloc_region_and_allocate(size_t word_size,
                                                       bool force) {
  // 獲取新的 Region
  HeapRegion* new_alloc_region = allocate_new_region(word_size, force);
  if (new_alloc_region != NULL) {
    new_alloc_region->reset_pre_dummy_top();
    // Need to do this before the allocation
    _used_bytes_before = new_alloc_region->used();
    // 在新的 Region 中分配
    HeapWord* result = allocate(new_alloc_region, word_size);

    OrderAccess::storestore();
    // Note that we first perform the allocation and then we store the
    // region in _alloc_region. This is the reason why an active region
    // can never be empty.
    update_alloc_region(new_alloc_region);
    return result;
  } else {
    return NULL;
  }
}

方法邏輯清晰簡單,首先要嘗試獲取新的 Region,成功后則在其上分配 TLAB。

2.3.2.1.1 步驟一 - 獲取新 Region

需要注意是方法中調(diào)用的 allocate_new_region() 方法,是 MutatorAllocRegion::allocate_new_region() 方法,而不是 G1GCAllocRegion::allocate_new_region() 方法。

# src/hotspot/share/gc/g1/g1AllocRegion.cpp

HeapRegion* MutatorAllocRegion::allocate_new_region(size_t word_size,
                                                    bool force) {
  return _g1h->new_mutator_alloc_region(word_size, force);
}

繼續(xù)看 _g1h->new_mutator_alloc_region() 方法。

# src/hotspot/share/gc/g1/g1CollectedHeap.cpp

HeapRegion* G1CollectedHeap::new_mutator_alloc_region(size_t word_size,
                                                      bool force) {
  bool should_allocate = policy()->should_allocate_mutator_region();
  // 判斷是否需要生成新的 Region
  if (force || should_allocate) {
    HeapRegion* new_alloc_region = new_region(word_size,
                                              HeapRegionType::Eden,
                                              false /* do_expand */);
    if (new_alloc_region != NULL) {
      set_region_short_lived_locked(new_alloc_region);
      _hr_printer.alloc(new_alloc_region, !should_allocate);
      _verifier->check_bitmaps("Mutator Region Allocation", new_alloc_region);
      _policy->remset_tracker()->update_at_allocate(new_alloc_region);
      return new_alloc_region;
    }
  }
  return NULL;
}

其中 force 值為 false,policy()->should_allocate_mutator_region() 方法判斷當(dāng)前 Eden 區(qū)和 Survivor 區(qū)中總的 Region 個數(shù)是否小于閾值 _young_list_target_length,該閾值一般在 GC 之后,通過 G1Policy::young_list_target_lengths() 方法來更新維護的,表示當(dāng)前 JVM 計算得出的年輕代分區(qū)數(shù)的一個合理值。

這里簡單看一下 policy()->should_allocate_mutator_region() 方法。

# src/hotspot/share/gc/g1/g1Policy.cpp

bool G1Policy::should_allocate_mutator_region() const {
  uint young_list_length = _g1h->young_regions_count();
  uint young_list_target_length = _young_list_target_length;
  return young_list_length < young_list_target_length;
}


# src/hotspot/share/gc/g1/g1CollectedHeap.hpp

uint young_regions_count() const { return _eden.length() + _survivor.length(); }

為了繼續(xù)往后進行分析,這里假定 should_allocatetrue,表示可以分配新的 Region,那么程序就來到 new_region() 方法。

HeapRegion* G1CollectedHeap::new_region(size_t word_size, HeapRegionType type, bool do_expand) {
  // 分配新的分區(qū)
  HeapRegion* res = _hrm->allocate_free_region(type);

  // 判斷是否進行擴展
  if (res == NULL && do_expand && _expand_heap_after_alloc_failure) {
    // Currently, only attempts to allocate GC alloc regions set
    // do_expand to true. So, we should only reach here during a
    // safepoint. If this assumption changes we might have to
    // reconsider the use of _expand_heap_after_alloc_failure.
    assert(SafepointSynchronize::is_at_safepoint(), "invariant");

    // 執(zhí)行擴展
    if (expand(word_size * HeapWordSize)) {
      // Given that expand() succeeded in expanding the heap, and we
      // always expand the heap by an amount aligned to the heap
      // region size, the free list should in theory not be empty.
      // In either case allocate_free_region() will check for NULL.
      res = _hrm->allocate_free_region(type);
    } else {
      _expand_heap_after_alloc_failure = false;
    }
  }
  return res;
}

方法首先調(diào)用 _hrm->allocate_free_region() 方法來獲取新的 Region,方法很簡單,就是從 _free_list 中獲取一個空閑分區(qū)。如果當(dāng)前空閑分區(qū)列表為空,由于 do_expand 值為 false,那么當(dāng)前流程并不會對分區(qū)進行擴展,而是直接返回 NULL。

# src/hotspot/share/gc/g1/heapRegionManager.hpp

virtual HeapRegion* allocate_free_region(HeapRegionType type) {
  // 從空閑分區(qū)列表中獲取一個分區(qū)
  HeapRegion* hr = _free_list.remove_region(!type.is_young());

  if (hr != NULL) {
    assert(hr->next() == NULL, "Single region should not have next");
    assert(is_available(hr->hrm_index()), "Must be committed");
  }
  return hr;
}

到這里,獲取新的 Region 的流程就結(jié)束了,回到之前的 new_alloc_region_and_allocate() 方法,如果獲取的新 Region 不為空,則會調(diào)用 allocate() 方法在新的 Region 中分配 TLAB。

2.3.2.1.2 步驟一 - 新 Region 中分配 TLAB
# src/hotspot/share/gc/g1/g1AllocRegion.inline.hpp

inline HeapWord* G1AllocRegion::allocate(HeapRegion* alloc_region,
                                         size_t word_size) {
  if (!_bot_updates) {
    return alloc_region->allocate_no_bot_updates(word_size);
  } else {
    return alloc_region->allocate(word_size);
  }
}

可以發(fā)現(xiàn)該方法跟之前的 par_allocate() 方法很相似,不同的是當(dāng)前方法不需要考慮并發(fā)問題,原因是當(dāng)前線程在之前已經(jīng)獲取了堆的全局鎖。根據(jù)之前 par_allocate() 方法分析可知,這里將執(zhí)行 alloc_region->allocate_no_bot_updates() 方法。

# src/hotspot/share/gc/g1/heapRegion.inline.hpp

inline HeapWord* HeapRegion::allocate_no_bot_updates(size_t word_size) {
  size_t temp;
  return allocate_no_bot_updates(word_size, word_size, &temp);
}


inline HeapWord* HeapRegion::allocate_no_bot_updates(size_t min_word_size,
                                                     size_t desired_word_size,
                                                     size_t* actual_word_size) {
  return allocate_impl(min_word_size, desired_word_size, actual_word_size);
}


inline HeapWord* G1ContiguousSpace::allocate_impl(size_t min_word_size,
                                                  size_t desired_word_size,
                                                  size_t* actual_size) {
  HeapWord* obj = top();
  size_t available = pointer_delta(end(), obj);
  size_t want_to_allocate = MIN2(available, desired_word_size);
  if (want_to_allocate >= min_word_size) {
    HeapWord* new_top = obj + want_to_allocate;
    set_top(new_top);
    *actual_size = want_to_allocate;
    return obj;
  } else {
    return NULL;
  }
}

方法最終調(diào)用了 G1ContiguousSpace::allocate_impl() 方法,由于是在加鎖的狀態(tài),這里直接執(zhí)行了 TLAB 空間的分配。

到這里,在堆加鎖的情況下分配 TLAB 就結(jié)束了,接下就是 [G1 慢速分配 TLAB - 步驟一] 的后半部分。

2.3.2.1.3 步驟一 - 嘗試強制分配

為了方便閱讀,這里再次把慢速分配 TLAB 方法(步驟一)的代碼貼出來:

# src/hotspot/share/gc/g1/g1CollectedHeap.cpp - attempt_allocation_slow()

// 步驟一
{
  // 堆加鎖,并嘗試分配
  MutexLocker x(Heap_lock);
  result = _allocator->attempt_allocation_locked(word_size);
  if (result != NULL) {
    return result;
  }

  // -------------------- 前后部分的分隔符 --------------------

  // If the GCLocker is active and we are bound for a GC, try expanding young gen.
  // This is different to when only GCLocker::needs_gc() is set: try to avoid
  // waiting because the GCLocker is active to not wait too long.
  // 如果 GCLocker 處于活動狀態(tài)并且必須要進行 GC,說明很快將會執(zhí)行一次 GC,
  // 再加上 Young Region 還有擴展的可能,就可以強制分配一個新的 Region。
  if (GCLocker::is_active_and_needs_gc() && policy()->can_expand_young_list()) {
    result = _allocator->attempt_allocation_force(word_size);
    if (result != NULL) {
      return result;
    }
  }
  // Only try a GC if the GCLocker does not signal the need for a GC. Wait until
  // the GCLocker initiated GC has been performed and then retry. This includes
  // the case when the GC Locker is not active but has not been performed.
  // 僅當(dāng) GCLocker 狀態(tài)為不需要使用 GC 時,才嘗試進行 GC。
  should_try_gc = !GCLocker::needs_gc();
  // Read the GC count while still holding the Heap_lock.
  gc_count_before = total_collections();
}

在滿足條件的情況下,會通過 attempt_allocation_force() 方法嘗試進行強制分配,該方法核心也是調(diào)用 G1AllocRegion::new_alloc_region_and_allocat() 方法,不同的是這次參數(shù) force 的值為 true。最后會在 G1CollectedHeap::new_region() 方法中執(zhí)行 G1CollectedHeap::expand() 擴展方法。

擴展方法的核心是將可能存在的不可用分區(qū)變?yōu)榭捎?,具體分析這里就不做介紹。

慢速分配 TLAB 方法的步驟一就分析完了。

2.3.2.2. G1 慢速分配 TLAB - 步驟二

接下來就是步驟二,大致的流程說明可以看代碼中的注釋。

# src/hotspot/share/gc/g1/g1CollectedHeap.cpp - attempt_allocation_slow()

// 步驟二
if (should_try_gc) {
  bool succeeded;
  // 執(zhí)行 GC,并返回分配的地址
  result = do_collection_pause(word_size, gc_count_before, &succeeded,
                               GCCause::_g1_inc_collection_pause);
  if (result != NULL) {
    return result;
  }

  // 成功執(zhí)行 GC 之后,還是分配失敗,則返回 NULL。
  if (succeeded) {
    // We successfully scheduled a collection which failed to allocate. No
    // point in trying to allocate further. We'll just return NULL.
    return NULL;
  }
} else {
  // Failed to schedule a collection.
  // 各種嘗試都失敗后,判斷嘗試的次數(shù)是否到達最大嘗試次數(shù),到達則返回 NULL。
  if (gclocker_retry_count > GCLockerRetryAllocationCount) {
    return NULL;
  }
  // The GCLocker is either active or the GCLocker initiated
  // GC has not yet been performed. Stall until it is and
  // then retry the allocation.
  // 線程自旋等待 GCLocker 需要 GC 的標(biāo)志位被清除,這樣可以在下次循環(huán)中再次嘗試 GC。
  GCLocker::stall_until_clear();
  gclocker_retry_count += 1;
}

// We can reach here if we were unsuccessful in scheduling a
// collection (because another thread beat us to it) or if we were
// stalled due to the GC locker. In either can we should retry the
// allocation attempt in case another thread successfully
// performed a collection and reclaimed enough space. We do the
// first attempt (without holding the Heap_lock) here and the
// follow-on attempt will be at the start of the next loop
// iteration (after taking the Heap_lock).
// 到達這里,說明在執(zhí)行 GC 后分配失敗,或者線程自旋等待結(jié)束,不管是哪種情況,
// 考慮到其它線程可能已經(jīng)執(zhí)行完 GC 并獲得足夠的空間,我們都需要再次嘗試分配,
// 這次的嘗試是不對堆加鎖的(更快),如果失敗的話,下次循環(huán)開始時會進行加鎖分配。
size_t dummy = 0;
result = _allocator->attempt_allocation(word_size, word_size, &dummy);
if (result != NULL) {
  return result;
}

當(dāng)堆加鎖之后的多次嘗試分配失敗后,如果當(dāng)前循環(huán)需要嘗試執(zhí)行 GC,則會調(diào)用 do_collection_pause() 方法,通過垃圾回收的方式分配指定大小的空間,具體的回收過程,打算之后再寫文章細說,這里就不做分析了。否則的話,則根據(jù)情況執(zhí)行自旋等待,直到達到最大嘗試次數(shù)。

循環(huán)的最后,考慮其它線程可能已經(jīng)執(zhí)行完 GC 并獲得足夠的空間,則再次嘗試不加鎖的快速分配。

至此,全部的慢速分配 TLAB 的流程就介紹完了。

二、TLAB 外分配

現(xiàn)在回到開始的 MemAllocator::mem_allocate() 方法。

# src/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);
}

這次要看的是 allocate_outside_tlab() 方法。

# src/hotspot/share/gc/shared/memAllocator.cpp

HeapWord* MemAllocator::allocate_outside_tlab(Allocation& allocation) const {
  allocation._allocated_outside_tlab = true;
  HeapWord* mem = Universe::heap()->mem_allocate(_word_size, &allocation._overhead_limit_exceeded);
  if (mem == NULL) {
    return mem;
  }

  NOT_PRODUCT(Universe::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;
}

核心是 Universe::heap()->mem_allocate() 方法。

# src/hotspot/share/gc/g1/g1CollectedHeap.cpp

HeapWord*
G1CollectedHeap::mem_allocate(size_t word_size,
                              bool*  gc_overhead_limit_was_exceeded) {
  // 判斷對象是否是大對象
  if (is_humongous(word_size)) {
    return attempt_allocation_humongous(word_size);
  }
  size_t dummy = 0;
  return attempt_allocation(word_size, word_size, &dummy);
}

判斷當(dāng)前分配的大小是否滿足大對象(超過 Region 大小的一半),如果不是大對象,則通過 attempt_allocation() 方法在 Region 中直接分配空間,之前的流程中有使用該方法來分配新的 TLAB,所以這里就不再贅述。

1. 分配大對象

如果是大對象,則執(zhí)行 attempt_allocation_humongous() 方法進行分配,該方法和 attempt_allocation_slow() 方法非常的相似,實際上,以前的版本中,這兩個方法是合并在一起的,但是由于不方便閱讀和不好追蹤異常,后來才被拆分為兩個獨立的方法。

由于方法代碼較長,我們還是一步步來看。

# src/hotspot/share/gc/g1/g1CollectedHeap.cpp - attempt_allocation_humongous()

// Humongous objects can exhaust the heap quickly, so we should check if we
// need to start a marking cycle at each humongous object allocation. We do
// the check before we do the actual allocation. The reason for doing it
// before the allocation is that we avoid having to keep track of the newly
// allocated memory while we do a GC.
// 嘗試在分配大對象之前,開啟并發(fā)標(biāo)記
if (policy()->need_to_start_conc_mark("concurrent humongous allocation",
                                           word_size)) {
  collect(GCCause::_g1_humongous_allocation);
}

在分配大對象之前,方法會嘗試開啟并發(fā)標(biāo)記,以便在之后執(zhí)行 GC 時,可以不必跟蹤標(biāo)記新分配的內(nèi)存。

方法接下來又是在一個循環(huán)體中嘗試進行分配,簡單分為以下兩個步驟。

1.1. 步驟一 - 嘗試分配

該步驟和【G1 慢速分配 TLAB - 步驟二】非常類似,首先都要對堆進行加鎖,然后再進行分配。

# src/hotspot/share/gc/g1/g1CollectedHeap.cpp - attempt_allocation_humongous()

// We will loop until a) we manage to successfully perform the
// allocation or b) we successfully schedule a collection which
// fails to perform the allocation. b) is the only case when we'll
// return NULL.
HeapWord* result = NULL;
for (uint try_count = 1, gclocker_retry_count = 0; /* we'll return */; try_count += 1) {
  bool should_try_gc;
  uint gc_count_before;

  {
    MutexLocker x(Heap_lock);

    // Given that humongous objects are not allocated in young
    // regions, we'll first try to do the allocation without doing a
    // collection hoping that there's enough space in the heap.
    // 由于大對象并非在新生代分區(qū)進行分配,所以嘗試在不進行 GC 的情況下進行首次分配。
    result = humongous_obj_allocate(word_size);
    if (result != NULL) {
      size_t size_in_regions = humongous_obj_size_in_regions(word_size);
      policy()->add_bytes_allocated_in_old_since_last_gc(size_in_regions * HeapRegion::GrainBytes);
      return result;
    }

    // Only try a GC if the GCLocker does not signal the need for a GC. Wait until
    // the GCLocker initiated GC has been performed and then retry. This includes
    // the case when the GC Locker is not active but has not been performed.
    // 僅當(dāng) GCLocker 狀態(tài)為不需要使用 GC 時,才嘗試進行 GC。
    should_try_gc = !GCLocker::needs_gc();
    // Read the GC count while still holding the Heap_lock.
    gc_count_before = total_collections();
  }

  // 后面的代碼暫時省略
  ...
}
  1. 分配前對堆加鎖。
  2. 調(diào)用 humongous_obj_allocate () 方法進行大對象分配。
  3. 分配成功,記錄 Old 區(qū)使用的空間大小。
  4. 分配失敗,判斷是否需要嘗試進行 GC。

繼續(xù)看 humongous_obj_allocate () 方法。

# src/hotspot/share/gc/g1/g1CollectedHeap.cpp

// If could fit into free regions w/o expansion, try.
// Otherwise, if can expand, do so.
// Otherwise, if using ex regions might help, try with ex given back.
HeapWord* G1CollectedHeap::humongous_obj_allocate(size_t word_size) {

  uint first = G1_NO_HRM_INDEX;
  // 計算保存大對象需要的 Region 數(shù)量
  uint obj_regions = (uint) humongous_obj_size_in_regions(word_size);

  if (obj_regions == 1) {
    // Only one region to allocate, try to use a fast path by directly allocating
    // from the free lists. Do not try to expand here, we will potentially do that
    // later.
    // 如果只需要一個 Region,則嘗試使用快速分配 Region,并且不嘗試進行擴展,
    // 因為之后可能會進行擴展。
    HeapRegion* hr = new_region(word_size, HeapRegionType::Humongous, false /* do_expand */);
    if (hr != NULL) {
      first = hr->hrm_index();
    }
  } else {
    // Policy: Try only empty regions (i.e. already committed first). Maybe we
    // are lucky enough to find some.
    // 嘗試從全部的 Region 中找到指定數(shù)量且連續(xù)的空 Region。
    first = _hrm->find_contiguous_only_empty(obj_regions);
    if (first != G1_NO_HRM_INDEX) {
      _hrm->allocate_free_regions_starting_at(first, obj_regions);
    }
  }

  // 如果分配 Region 失敗
  if (first == G1_NO_HRM_INDEX) {
    // Policy: We could not find enough regions for the humongous object in the
    // free list. Look through the heap to find a mix of free and uncommitted regions.
    // If so, try expansion.
    first = _hrm->find_contiguous_empty_or_unavailable(obj_regions);
    if (first != G1_NO_HRM_INDEX) {
      // We found something. Make sure these regions are committed, i.e. expand
      // the heap. Alternatively we could do a defragmentation GC.

      _hrm->expand_at(first, obj_regions, workers());
      policy()->record_new_heap_size(num_regions());

      _hrm->allocate_free_regions_starting_at(first, obj_regions);
    } else {
      // Policy: Potentially trigger a defragmentation GC.
    }
  }

  HeapWord* result = NULL;
  if (first != G1_NO_HRM_INDEX) {
    result = humongous_obj_allocate_initialize_regions(first, obj_regions, word_size);

    // A successful humongous object allocation changes the used space
    // information of the old generation so we need to recalculate the
    // sizes and update the jstat counters here.
    g1mm()->update_sizes();
  }
  return result;
}
  1. 計算保存大對象需要的 Region 數(shù)量。
  2. 如果只需要一個 Region,則嘗試使用快速分配新的 Region,并且不嘗試進行擴展。
  3. 如果需要多個 Region,則嘗試從全部的 Region 中找到連續(xù)指定數(shù)量的空的 Region。
  4. 如果分配 Region 失敗,則嘗試從全部的 Region 中找到連續(xù)指定數(shù)量的空的或者未提交的 Region,如找到這樣連續(xù)的 Region 的話,為了確保其中未提交的 Region 被提交,我們需要對堆進行擴展,或者進行 GC。
  5. 如果最終分配成功,則初始化分配的 Regions,并更新內(nèi)部跟 Region 相關(guān)的統(tǒng)計字段。
1.2. 步驟二 - 嘗試 GC 后再分配
# src/hotspot/share/gc/g1/g1CollectedHeap.cpp - attempt_allocation_humongous()

// We will loop until a) we manage to successfully perform the
// allocation or b) we successfully schedule a collection which
// fails to perform the allocation. b) is the only case when we'll
// return NULL.
HeapWord* result = NULL;
for (uint try_count = 1, gclocker_retry_count = 0; /* we'll return */; try_count += 1) {
  bool should_try_gc;
  uint gc_count_before;

  // 之前嘗試為大對象分配的代碼省略
  ...

  if (should_try_gc) {
    bool succeeded;
    // 為回收指定大小的空間而執(zhí)行 GC 操作
    result = do_collection_pause(word_size, gc_count_before, &succeeded,
                                   GCCause::_g1_humongous_allocation);
    if (result != NULL) {
      return result;
    }

    if (succeeded) {
      // We successfully scheduled a collection which failed to allocate. No
      // point in trying to allocate further. We'll just return NULL.
      // 成功執(zhí)行 GC 之后,還是分配失敗,則返回 NULL。
      return NULL;
    }
  } else {
    // Failed to schedule a collection.
    // 判斷嘗試分配次數(shù)是否大于最大嘗試次數(shù),大于則返回 NULL。
    if (gclocker_retry_count > GCLockerRetryAllocationCount) {
      return NULL;
    }
    // The GCLocker is either active or the GCLocker initiated
    // GC has not yet been performed. Stall until it is and
    // then retry the allocation.
    // 線程自旋等待 GCLocker 需要 GC 的標(biāo)志位被清除,這樣可以在下次循環(huán)中再次嘗試。
    GCLocker::stall_until_clear();
    gclocker_retry_count += 1;
  }
}

這塊代碼跟【G1 慢速分配 TLAB - 步驟二】中分析的代碼基本一致,這里就不再贅述。

到這,G1 中對象分配的全部過程就都分析完了,感謝閱讀。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容