src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp
//在TLAB慢分配被调用,当前 TLAB 放回堆 void ThreadLocalAllocBuffer::retire_before_allocation() { //将当前 TLAB 剩余空间大小加入慢分配浪费空间大小 _slow_refill_waste += (unsigned int)remaining(); //执行 TLAB 退还给堆,这个在后面 GC 的时候还会被调用用于将所有的线程的 TLAB 退回堆 retire(); } //对于 TLAB 慢分配,stats 为空 //对于 GC 的时候调用,stats 用于记录每个线程的数据 void ThreadLocalAllocBuffer::retire(ThreadLocalAllocStats* stats) { if (stats != NULL) { accumulate_and_reset_statistics(stats); } //如果当前 TLAB 有效 if (end() != NULL) { invariants(); //将用了的空间记录如线程分配对象大小记录 thread()->incr_allocated_bytes(used_bytes()); //填充dummy object insert_filler(); //清空当前 TLAB 指针 initialize(NULL, NULL, NULL); } } 9.4. GC 相关 TLAB 操作 9.4.1. GC 前不同的 GC 可能实现不一样,但是 TLAB 操作的时机是基本一样的,这里以 G1 GC 为例,在真正 GC 前:
src/hotspot/share/gc/g1/g1CollectedHeap.cpp
void G1CollectedHeap::gc_prologue(bool full) { //省略其他代码 // Fill TLAB's and such { Ticks start = Ticks::now(); //确保堆内存是可以解析的 ensure_parsability(true); Tickspan dt = Ticks::now() - start; phase_times()->record_prepare_tlab_time_ms(dt.seconds() * MILLIUNITS); } //省略其他代码 }为何要确保堆内存是可以解析的呢?这样有利于更快速的扫描堆上对象。确保内存可以解析里面做了什么呢?其实主要就是退还每个线程的 TLAB 以及填充 dummy object。
src/hotspot/share/gc/g1/g1CollectedHeap.cpp
void CollectedHeap::ensure_parsability(bool retire_tlabs) { //真正的 GC 肯定发生在安全点上,这个在后面安全点章节会详细说明 assert(SafepointSynchronize::is_at_safepoint() || !is_init_completed(), "Should only be called at a safepoint or at start-up"); ThreadLocalAllocStats stats; for (JavaThreadIteratorWithHandle jtiwh; JavaThread *thread = jtiwh.next();) { BarrierSet::barrier_set()->make_parsable(thread); //如果全局启用了 TLAB if (UseTLAB) { //如果指定要回收,则回收 TLAB if (retire_tlabs) { //回收 TLAB,调用 9.3.2.3. 当前 TLAB 放回堆 提到的 retire 方法 thread->tlab().retire(&stats); } else { //当前如果不回收,则将 TLAB 填充 Dummy Object 利于解析 thread->tlab().make_parsable(); } } } stats.publish(); } 9.4.2. GC 后不同的 GC 可能实现不一样,但是 TLAB 操作的时机是基本一样的,这里以 G1 GC 为例,在 GC 后:
src/hotspot/share/gc/g1/g1CollectedHeap.cpp
_desired_size是什么时候变得呢?怎么变得呢?
src/hotspot/share/gc/shared/collectedHeap.cpp
void CollectedHeap::resize_all_tlabs() { //需要在安全点,GC 会处于安全点的 assert(SafepointSynchronize::is_at_safepoint() || !is_init_completed(), "Should only resize tlabs at safepoint"); //如果 UseTLAB 和 ResizeTLAB 都是打开的(默认就是打开的) if (UseTLAB && ResizeTLAB) { for (JavaThreadIteratorWithHandle jtiwh; JavaThread *thread = jtiwh.next(); ) { //重新计算每个线程 TLAB 期望大小 thread->tlab().resize(); } } }重新计算每个线程 TLAB 期望大小:
src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp
这里我会持续更新的,解决大家的各种疑问
10.1. 为何 TLAB 在退还给堆的时候需要填充 dummy object主要保证 GC 的时候扫描高效。由于 TLAB 仅线程内知道哪些被分配了,在 GC 扫描发生时返回 Eden 区,如果不填充的话,外部并不知道哪一部分被使用哪一部分没有,需要做额外的检查,如果填充已经确认会被回收的对象,也就是 dummy object, GC 会直接标记之后跳过这块内存,增加扫描效率。反正这块内存已经属于 TLAB,其他线程在下次扫描结束前是无法使用的。这个 dummy object 就是 int 数组。为了一定能有填充 dummy object 的空间,一般 TLAB 大小都会预留一个 dummy object 的 header 的空间,也是一个 int[] 的 header,所以 TLAB 的大小不能超过int 数组的最大大小,否则无法用 dummy object 填满未使用的空间。
10.2. 为何 TLAB 需要最大浪费空间限制