JVM 源码分析 - G1 对象分配全流程

前言

Java 程序中,我们经常会创建各种各样的对象实例,我们也都知道,绝大部分的对象实例都是在堆中进行分配的,但是 JVM 到底是如何在堆上为对象实例分配内存空间的呢?下面笔者就以 OpenJDK 13G1 垃圾回收器为例,通过源码来详细了解一下分配的过程。

对象分配

我们以 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);
}
  1. 如果开启使用 TLAB,则先尝试在 TLAB 上分配,成功则直接返回。
  2. 如果未开启使用 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() 方法来实现 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,该字段值是在创建分区分配器的时候就已经确定,如下:

# 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 值为 falsepolicy()->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_allocatetrue,表示可以分配新的 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();
  }

  // 后面的代码暂时省略
  ...
}
  1. 分配前对堆加锁。
  2. 调用 humongous_obj_allocate () 方法进行大对象分配。
  3. 分配成功,记录 Old 区使用的空间大小。
  4. 分配失败,判断是否需要尝试进行 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;
}
  1. 计算保存大对象需要的 Region 数量。
  2. 如果只需要一个 Region,则尝试使用快速分配新的 Region,并且不尝试进行扩展。
  3. 如果需要多个 Region,则尝试从全部的 Region 中找到连续指定数量的空的 Region
  4. 如果分配 Region 失败,则尝试从全部的 Region 中找到连续指定数量的空的或者未提交的 Region,如找到这样连续的 Region 的话,为了确保其中未提交的 Region 被提交,我们需要对堆进行扩展,或者进行 GC
  5. 如果最终分配成功,则初始化分配的 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 中对象分配的全部过程就都分析完了,感谢阅读。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272