前言
在 Java 程序中,我們經(jīng)常會創(chuàng)建各種各樣的對象實例,我們也都知道,絕大部分的對象實例都是在堆中進行分配的,但是 JVM 到底是如何在堆上為對象實例分配內(nèi)存空間的呢?下面筆者就以 OpenJDK 13 中 G1 垃圾回收器為例,通過源碼來詳細了解一下分配的過程。
對象分配
我們以 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);
}
- 如果開啟使用
TLAB,則先嘗試在TLAB上分配,成功則直接返回。 - 如果未開啟使用
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) TLAB 在 Region 中的分配的,我們來具體看一下。
# 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_allocate 為 true,表示可以分配新的 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();
}
// 后面的代碼暫時省略
...
}
- 分配前對堆加鎖。
- 調(diào)用
humongous_obj_allocate ()方法進行大對象分配。 - 分配成功,記錄
Old區(qū)使用的空間大小。 - 分配失敗,判斷是否需要嘗試進行
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;
}
- 計算保存大對象需要的 Region 數(shù)量。
- 如果只需要一個
Region,則嘗試使用快速分配新的Region,并且不嘗試進行擴展。 - 如果需要多個
Region,則嘗試從全部的Region中找到連續(xù)指定數(shù)量的空的Region。 - 如果分配
Region失敗,則嘗試從全部的Region中找到連續(xù)指定數(shù)量的空的或者未提交的Region,如找到這樣連續(xù)的Region的話,為了確保其中未提交的Region被提交,我們需要對堆進行擴展,或者進行GC。 - 如果最終分配成功,則初始化分配的
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 中對象分配的全部過程就都分析完了,感謝閱讀。