全网最硬核 JVM TLAB 分析(单篇版不包含额外加菜) (8)

src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp

//计算初始大小 size_t ThreadLocalAllocBuffer::initial_desired_size() { size_t init_sz = 0; //如果通过 -XX:TLABSize 设置了 TLAB 大小,则用这个值作为初始期望大小 //表示堆内存占用大小都需要用占用几个 HeapWord 表示,所以用TLABSize / HeapWordSize if (TLABSize > 0) { init_sz = TLABSize / HeapWordSize; } else { //获取当前epoch内线程数量期望,这个如之前所述通过 EMA 预测 unsigned int nof_threads = ThreadLocalAllocStats::allocating_threads_avg(); //不同的 GC 实现有不同的 TLAB 容量,Universe::heap()->tlab_capacity(thread()) 一般是 Eden 区大小 //例如 G1 GC,就是等于 (_policy->young_list_target_length() - _survivor.length()) * HeapRegion::GrainBytes,可以理解为年轻代减去Survivor区,也就是Eden区 //整体大小等于 Eden区大小/(当前 epcoh 内会分配对象期望线程个数 * 每个 epoch 内每个线程 refill 次数配置) //target_refills已经在 JVM 初始化所有 TLAB 全局配置的时候初始化好了 init_sz = (Universe::heap()->tlab_capacity(thread()) / HeapWordSize) / (nof_threads * target_refills()); //考虑对象对齐,得出最后的大小 init_sz = align_object_size(init_sz); } //保持大小在 min_size() 还有 max_size() 之间 //min_size主要由 MinTLABSize 决定 init_sz = MIN2(MAX2(init_sz, min_size()), max_size()); return init_sz; } //最小大小由 MinTLABSize 决定,需要表示为 HeapWordSize,并且考虑对象对齐,最后的 alignment_reserve 是 dummy object 填充的对象头大小(这里先不考虑 JVM 的 CPU 缓存 prematch,我们会在其他章节详细分析)。 static size_t min_size() { return align_object_size(MinTLABSize / HeapWordSize) + alignment_reserve(); } 9.2.2. TLAB 最大大小是怎样决定的呢?

不同的 GC 方式,有不同的方式:

G1 GC 中为大对象(humongous object)大小,也就是 G1 region 大小的一半:src/hotspot/share/gc/g1/g1CollectedHeap.cpp

// For G1 TLABs should not contain humongous objects, so the maximum TLAB size // must be equal to the humongous object limit. size_t G1CollectedHeap::max_tlab_size() const { return align_down(_humongous_object_threshold_in_words, MinObjAlignment); }

ZGC 中为页大小的 8 分之一,类似的在大部分情况下 Shenandoah GC 也是每个 Region 大小的 8 分之一。他们都是期望至少有 8 分之 7 的区域是不用退回的减少选择 Cset 的时候的扫描复杂度:
src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp

MaxTLABSizeWords = MIN2(ShenandoahElasticTLAB ? RegionSizeWords : (RegionSizeWords / 8), HumongousThresholdWords);

src/hotspot/share/gc/z/zHeap.cpp

const size_t ZObjectSizeLimitSmall = ZPageSizeSmall / 8;

对于其他的 GC,则是 int 数组的最大大小,这个和为了填充 dummy object 表示 TLAB 的空区域有关。这个原因之前已经说明了。

9.3. TLAB 分配内存

当 new 一个对象时,需要调用instanceOop InstanceKlass::allocate_instance(TRAPS)
src/hotspot/share/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; }

其核心就是heap()->obj_allocate(this, size, CHECK_NULL)从堆上面分配内存:
src/hotspot/share/gc/shared/collectedHeap.inline.hpp

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

使用全局的 ObjAllocator 实现进行对象内存分配:
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); } else { // The unhandled oop detector will poison local variable obj, // so reset it to NULL if mem is NULL. obj = NULL; } } return obj; } 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); } HeapWord* MemAllocator::allocate_inside_tlab(Allocation& allocation) const { assert(UseTLAB, "should use UseTLAB"); //从当前线程的 TLAB 分配内存,TLAB 快分配 HeapWord* mem = _thread->tlab().allocate(_word_size); //如果没有分配失败则返回 if (mem != NULL) { return mem; } //如果分配失败则走 TLAB 慢分配,需要 refill 或者直接从 Eden 分配 return allocate_inside_tlab_slow(allocation); } 9.3.1. TLAB 快分配

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppzys.html