前言
在 Java
程序中,我们经常会创建各种各样的对象实例,我们也都知道,绝大部分的对象实例都是在堆中进行分配的,但是 JVM 到底是如何在堆上为对象实例分配内存空间的呢?下面笔者就以 OpenJDK 13
中 G1
垃圾回收器为例,通过源码来详细了解一下分配的过程。
对象分配
我们以 new
关键字创建对象为例。
public class Test {
public static void main(String[] args) {
Test t = new Test();
}
}
NEW 关键字
编译过后,执行 javap -v
命令可以看到 main()
方法对应的字节码。
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
指令对应的处理逻辑在 src/share/vm/interpreter/bytecodeInterpretor.cpp
中,相关代码如下:
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
上进行分配(在慢速分配中会详细介绍),如果失败的话,则会执行 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
方法首先对类进行加载和初始化,重点看调用 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
的堆空间,管理的方式根据配置的不同垃圾回收器而有所不同,可以在该类的注释上看到当前 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 对象分配
言归正传,继续深入 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()
方法,所以最后执行的是 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;
}
方法继续调用 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
,是线程的一块私有内存,如果设置了虚拟机参数-XX:UseTLAB
,在线程初始化时,同时也会分配一块指定大小的内存,只给当前线程使用,这样每个线程都单独拥有一个Buffer
,如果需要分配内存,就在自己的Buffer
上分配,这样就不存在竞争的情况,可以大大提升分配效率,当Buffer
容量不够的时候,再重新从Eden
区域分配一块继续使用,这个分配动作还是需要原子操作的。更多参考: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 上分配 - 快速分配
快速分配很简单,即在当前线程所拥有的 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 上分配 - 慢速分配
慢速分配是在快速分配失败之后的方案,也就当前线程所拥有的 TLAB
上空间不足,所以慢速分配的核心就是分配新的 TLAB
,然后再在其上进行对象分配。下面是慢速分配的完整代码:
# src/hotspot/share/gc/shared/memAllocator.cpp
HeapWord* MemAllocator::allocate_inside_tlab_slow(Allocation& allocation) const {
HeapWord* mem = NULL;
// 获取当前线程对应的 TLAB
ThreadLocalAllocBuffer& tlab = _thread->tlab();
// 步骤一
// 如果当前 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.
// 步骤二
// 如果当前 TLAB 的剩余容量大于浪费的限制值,则记录这次的慢分配,并且会向上微调限制值。
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);
// 废弃当前线程拥有的 TLAB
tlab.retire_before_allocation();
// 如果新的 TLAB 的大小为零,说明当前没有足够大的空间。
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 开始的对象头大小的空间写入“坏值”,这样任何并发的 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()
// 步骤一
// 如果当前 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)
// 分配结尾(可以是采样终点,或者 _allocation_end)
HeapWord* _allocation_end; // end for allocations (actual TLAB end, excluding alignment_reserve)
// 实际结尾(TLAB 真实结尾)
...
}
从注释可以得知,_end
位置是小于 _allocation_end
的,两者之间的空间应该就是用于存放采样对象的,tlab.set_back_allocation_end()
方法就是释放这块的空间,然后调用 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.
// 步骤二
// 如果当前 TLAB 的剩余空间超过允许浪费的阈值,则记录这次的慢分配,并且会向上微调阈值。
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);
// 废弃当前线程拥有的 TLAB
tlab.retire_before_allocation();
// 如果新的 TLAB 的大小为零,说明当前没有足够大的空间。
if (new_tlab_size == 0) {
return NULL;
}
该步骤首先会先判断当前 TLAB
中剩余的空间是否超过允许浪费的阈值,超过的话,说明剩余空间还比较多,不应该浪费,那此时会记录这次慢分配,同时会向上微调该阈值,之后对象会尝试在 TLAB
之外进行分配;如果不超过的话,说明剩余空间很少,可以废弃当前线程拥有的 TLAB
,然后重新分配新的 TLAB
(步骤三)。
顺便说一下,该阈值可以使用 TLABRefillWasteFraction
来调整,它表示 TLAB
中可以接受的浪费空间的比例,默认值为 64
,即表示当 TLAB
中剩余空间不足整个的 1/64
时,可以废弃当前 TLAB
,创建新的使用,否则要尽量使用当前的 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
中的实现:
# 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);
}
继续深入看一下 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) {
// 尝试在当前 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;
}
可以看出,在分配新的内存空间时,同样包括 _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) {
// 尝试在保留分区中分配
HeapWord* result = mutator_alloc_region()->attempt_retained_allocation(min_word_size, desired_word_size, actual_word_size);
if (result != NULL) {
return result;
}
// 尝试在当前分区中分配
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()
尝试在当前使用的 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) {
// 判断保留分区是否存在
if (_retained_alloc_region != NULL) {
// 尝试在保留分区上并发分配
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;
// 尝试在当前分区上并发分配
HeapWord* result = par_allocate(alloc_region, min_word_size, desired_word_size, actual_word_size);
if (result != NULL) {
return result;
}
return NULL;
}
介绍一下保留分区 _retained_alloc_region
,之前有说到当 TLAB
中剩余空间小于浪费阈值时,可以废弃当前的 TLAB
,否则需要继续使用。同样 Region
分配和使用也有类似的规则,不同的是,如果将要废弃的 Region
中剩余的空间仍然可以容纳一个最小的 TLAB
时,则在使用新的 Region
的同时,记录该 Region
,也就是保留分区,这样之后分配新的 TLAB
时可能会用上,这样可以在一定程度上减少整体空间的浪费。
虽然两种分配所在的 Region
有所不同,但最终都是调用 par_allocate()
方法来实现 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
,该字段值是在创建分区分配器的时候就已经确定,如下:
# src/hotspot/share/gc/g1/g1AllocRegion.hpp
// Eden 区分配器
class MutatorAllocRegion : public G1AllocRegion {
...
public:
MutatorAllocRegion()
: G1AllocRegion("Mutator Alloc Region", false /* bot_updates */),
_wasted_bytes(0),
_retained_alloc_region(NULL) { }
...
};
// Survivor 区分配器
class SurvivorGCAllocRegion : public G1GCAllocRegion {
public:
SurvivorGCAllocRegion(G1EvacStats* stats)
: G1GCAllocRegion("Survivor GC Alloc Region", false /* bot_updates */, stats, G1HeapRegionAttr::Young) { }
};
// Old 区分配器
class OldGCAllocRegion : public G1GCAllocRegion {
public:
OldGCAllocRegion(G1EvacStats* stats)
: G1GCAllocRegion("Old GC Alloc Region", true /* bot_updates */, stats, G1HeapRegionAttr::Old) { }
...
};
由于我们是通过 new
关键字来创建对象实例,所以使用的是 Eden
区的分配器,那么 _bot_updates
值就为 false
。需要注意的是,如果实例是大对象的话,是不是就应该在 Old
区中分配?逻辑确实是这样的,但是大对象的分配并非在这里,而是在开头介绍的 MemAllocator::allocate_outside_tlab()
方法中实现,也就是在 TLAB
之外分配的。
所以当前会执行 alloc_region->par_allocate_no_bot_updates()
方法,该方法会再调用 par_allocate_impl()
方法,然后通过不断的使用 CAS
去竞争分配空间,直到当前 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();
// 获取当前 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 方式来分配空间,直到当前 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
方法中。该方法有点复杂,为了说明方便,同样将方法分为三个步骤,之后会依次进行分析。
先从整体上看一下,方法就是由一个大的循环体构成,在循环体中将会不断尝试为当前线程分配 TLAB
的空间,直到分配成功或者到达最大尝试次数。
# 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 处于活动状态并且必须要进行 GC,说明很快将会执行一次 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.
// 仅当 GCLocker 状态为不需要使用 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;
// 执行 GC,并返回分配的地址
result = do_collection_pause(word_size, gc_count_before, &succeeded,
GCCause::_g1_inc_collection_pause);
if (result != NULL) {
return result;
}
// 成功执行 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.
// 各种尝试都失败后,判断尝试的次数是否到达最大尝试次数,到达则返回 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 的标志位被清除,这样可以在下次循环中再次尝试 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).
// 到达这里,说明在执行 GC 后分配失败,或者线程自旋等待结束,不管是哪种情况,
// 考虑到其它线程可能已经执行完 GC 并获得足够的空间,我们都需要再次尝试分配,
// 这次的尝试是不对堆加锁的(更快),如果失败的话,下次循环开始时会进行加锁分配。
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 处于活动状态并且必须要进行 GC,说明很快将会执行一次 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.
// 仅当 GCLocker 状态为不需要使用 GC 时,才尝试进行 GC。
should_try_gc = !GCLocker::needs_gc();
// Read the GC count while still holding the Heap_lock.
gc_count_before = total_collections();
}
由于之前通过 CAS
方式的快速分配的失败,可能是因为并发分配竞争太过激烈,所以慢速分配方法首先就尝试对整个堆分区进行加锁,然后再调用 _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;
}
继续看 mutator_alloc_region()->attempt_allocation_locked()
方法,该方法又继续调用更多参数的 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.
// 首先再次尝试在当前 Region 中分配内存,主要是考虑当前线程在等待获取堆锁的时候,
// 可能有其它线程改变了当前的 Region(比如使用新的 Region)。
HeapWord* result = attempt_allocation(min_word_size, desired_word_size, actual_word_size);
if (result != NULL) {
return result;
}
// 废弃当前的 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;
}
考虑当前线程在等待获取堆锁的时候,其它线程有可能改变了当前的 Region
,比如使用了新的 Region
。所以方法首先再次尝试调用 attempt_allocation()
方法在当前 Region
中分配 TLAB
,该方法之前已经有分析了,这里就不在赘述。我们主要看一下在分配失败后,如何在 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
需要注意是方法中调用的 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);
}
继续看 _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()
方法判断当前 Eden
区和 Survivor
区中总的 Region
个数是否小于阈值 _young_list_target_length
,该阈值一般在 GC
之后,通过 G1Policy::young_list_target_lengths()
方法来更新维护的,表示当前 JVM
计算得出的年轻代分区数的一个合理值。
这里简单看一下 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(); }
为了继续往后进行分析,这里假定 should_allocate
为 true
,表示可以分配新的 Region
,那么程序就来到 new_region()
方法。
HeapRegion* G1CollectedHeap::new_region(size_t word_size, HeapRegionType type, bool do_expand) {
// 分配新的分区
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");
// 执行扩展
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;
}
方法首先调用 _hrm->allocate_free_region()
方法来获取新的 Region
,方法很简单,就是从 _free_list
中获取一个空闲分区。如果当前空闲分区列表为空,由于 do_expand
值为 false
,那么当前流程并不会对分区进行扩展,而是直接返回 NULL。
# src/hotspot/share/gc/g1/heapRegionManager.hpp
virtual HeapRegion* allocate_free_region(HeapRegionType type) {
// 从空闲分区列表中获取一个分区
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
的流程就结束了,回到之前的 new_alloc_region_and_allocate()
方法,如果获取的新 Region
不为空,则会调用 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);
}
}
可以发现该方法跟之前的 par_allocate()
方法很相似,不同的是当前方法不需要考虑并发问题,原因是当前线程在之前已经获取了堆的全局锁。根据之前 par_allocate()
方法分析可知,这里将执行 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;
}
}
方法最终调用了 G1ContiguousSpace::allocate_impl()
方法,由于是在加锁的状态,这里直接执行了 TLAB
空间的分配。
到这里,在堆加锁的情况下分配 TLAB
就结束了,接下就是 [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 处于活动状态并且必须要进行 GC,说明很快将会执行一次 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.
// 仅当 GCLocker 状态为不需要使用 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()
方法尝试进行强制分配,该方法核心也是调用 G1AllocRegion::new_alloc_region_and_allocat()
方法,不同的是这次参数 force
的值为 true
。最后会在 G1CollectedHeap::new_region()
方法中执行 G1CollectedHeap::expand()
扩展方法。
扩展方法的核心是将可能存在的不可用分区变为可用,具体分析这里就不做介绍。
慢速分配 TLAB
方法的步骤一就分析完了。
2.3.2.2. G1 慢速分配 TLAB - 步骤二
接下来就是步骤二,大致的流程说明可以看代码中的注释。
# src/hotspot/share/gc/g1/g1CollectedHeap.cpp - attempt_allocation_slow()
// 步骤二
if (should_try_gc) {
bool succeeded;
// 执行 GC,并返回分配的地址
result = do_collection_pause(word_size, gc_count_before, &succeeded,
GCCause::_g1_inc_collection_pause);
if (result != NULL) {
return result;
}
// 成功执行 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.
// 各种尝试都失败后,判断尝试的次数是否到达最大尝试次数,到达则返回 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 的标志位被清除,这样可以在下次循环中再次尝试 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).
// 到达这里,说明在执行 GC 后分配失败,或者线程自旋等待结束,不管是哪种情况,
// 考虑到其它线程可能已经执行完 GC 并获得足够的空间,我们都需要再次尝试分配,
// 这次的尝试是不对堆加锁的(更快),如果失败的话,下次循环开始时会进行加锁分配。
size_t dummy = 0;
result = _allocator->attempt_allocation(word_size, word_size, &dummy);
if (result != NULL) {
return result;
}
当堆加锁之后的多次尝试分配失败后,如果当前循环需要尝试执行 GC
,则会调用 do_collection_pause()
方法,通过垃圾回收的方式分配指定大小的空间,具体的回收过程,打算之后再写文章细说,这里就不做分析了。否则的话,则根据情况执行自旋等待,直到达到最大尝试次数。
循环的最后,考虑其它线程可能已经执行完 GC 并获得足够的空间,则再次尝试不加锁的快速分配。
至此,全部的慢速分配 TLAB
的流程就介绍完了。
二、TLAB 外分配
现在回到开始的 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);
}
判断当前分配的大小是否满足大对象(超过 Region
大小的一半),如果不是大对象,则通过 attempt_allocation()
方法在 Region
中直接分配空间,之前的流程中有使用该方法来分配新的 TLAB
,所以这里就不再赘述。
1. 分配大对象
如果是大对象,则执行 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.
// 尝试在分配大对象之前,开启并发标记
if (policy()->need_to_start_conc_mark("concurrent humongous allocation",
word_size)) {
collect(GCCause::_g1_humongous_allocation);
}
在分配大对象之前,方法会尝试开启并发标记,以便在之后执行 GC
时,可以不必跟踪标记新分配的内存。
方法接下来又是在一个循环体中尝试进行分配,简单分为以下两个步骤。
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.
// 由于大对象并非在新生代分区进行分配,所以尝试在不进行 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.
// 仅当 GCLocker 状态为不需要使用 GC 时,才尝试进行 GC。
should_try_gc = !GCLocker::needs_gc();
// Read the GC count while still holding the Heap_lock.
gc_count_before = total_collections();
}
// 后面的代码暂时省略
...
}
- 分配前对堆加锁。
- 调用
humongous_obj_allocate ()
方法进行大对象分配。 - 分配成功,记录
Old
区使用的空间大小。 - 分配失败,判断是否需要尝试进行
GC
。
继续看 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 数量
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 中找到指定数量且连续的空 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 数量。
- 如果只需要一个
Region
,则尝试使用快速分配新的Region
,并且不尝试进行扩展。 - 如果需要多个
Region
,则尝试从全部的Region
中找到连续指定数量的空的Region
。 - 如果分配
Region
失败,则尝试从全部的Region
中找到连续指定数量的空的或者未提交的Region
,如找到这样连续的Region
的话,为了确保其中未提交的Region
被提交,我们需要对堆进行扩展,或者进行GC
。 - 如果最终分配成功,则初始化分配的
Regions
,并更新内部跟Region
相关的统计字段。
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;
// 为回收指定大小的空间而执行 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.
// 成功执行 GC 之后,还是分配失败,则返回 NULL。
return NULL;
}
} else {
// Failed to schedule a collection.
// 判断尝试分配次数是否大于最大尝试次数,大于则返回 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 的标志位被清除,这样可以在下次循环中再次尝试。
GCLocker::stall_until_clear();
gclocker_retry_count += 1;
}
}
这块代码跟【G1 慢速分配 TLAB - 步骤二】中分析的代码基本一致,这里就不再赘述。
到这,G1 中对象分配的全部过程就都分析完了,感谢阅读。