G1如何与TLAB一起工作
TLAB在G1上,完全复用了小对象的内存分配流程,在选择region上,region内部使用指针碰撞等细节完全一致。可以认为TLAB是一个固定大小的小对象,G1按照这个大小分配了一个小对象空间,这块空间并没有用于存储一个小对象,而是用户存储多个小对象。
在G1垃圾收集器中,当线程的TLAB(Thread Local Allocation Buffer)申请的空间未被完全使用时,其处理机制涉及碎片管理、内存回收和GC优化策略。以下是详细流程:
🔍 1. TLAB剩余空间标记为碎片
- 碎片产生场景:
- 主动废弃:当TLAB剩余空间不足以分配新对象(但大于
refill_waste阈值)时,线程会废弃当前TLAB,剩余空间成为碎片。 - GC触发回收:Young GC发生时,所有TLAB无论是否用完均被废弃,未分配空间转为碎片。
- 主动废弃:当TLAB剩余空间不足以分配新对象(但大于
- 碎片本质:这些空间属于Eden Region的一部分,但无法再被分配使用,形成内存孔隙(Fragmentation)。
⚙️ 2. 填充哑元对象(Dummy Object)
为解决碎片对GC扫描效率的影响,G1采用以下步骤:
- 填充操作: 在TLAB被废弃或GC触发时,剩余空间会被一个特殊填充对象(Dummy Object) 占据。该对象仅包含对象头(约12-16字节),无实际数据。
- GC扫描优化:
- Dummy Object在GC标记阶段被直接识别为“死亡对象”,扫描时跳过其占据的内存区域。
- 避免无效扫描:若不填充,GC需遍历整个Region检查每个地址是否被使用,极大降低效率。
🔄 3. 空间回收时机
- Young GC统一回收:
所有Eden Region(包括含碎片的TLAB区域)在Young GC时被整体回收:
- 存活对象复制到Survivor Region。
- 碎片空间随整个Eden Region被清空,Region重置为空闲状态。
- 无单独回收机制: 碎片不会跨TLAB复用或合并,仅在GC时整体释放。
⚖️ 4. 设计权衡:空间浪费 vs 性能收益
- 空间代价:
TLAB碎片导致Eden区约 1%-5%的空间浪费(默认
TLABRefillWasteFraction=64时,浪费上限为TLAB的1/64)。 - 性能收益:
- 分配速度提升:TLAB无锁分配使小对象分配耗时降至10-15ns(对比共享Eden分配的50ns)。
- GC效率优化:Dummy Object减少扫描范围,Young GC停顿时间降低10%-30%。
🛠️ 5. 参数调优与监控
-
关键参数:
参数名 作用 默认值 TLABRefillWasteFraction动态计算最大浪费空间( refill_waste = TLAB大小 / 该值)64 TLABWasteIncrementTLAB分配失败时增加浪费空间上限的步长(单位:8字节的MarkWord个数) 4 ResizeTLAB是否根据历史分配数据动态调整TLAB大小 true -
监控工具:
- JFR(Java Flight Recorder):记录TLAB浪费比例、分配失败次数等指标。
- GC日志分析:通过
XX:+PrintTLAB查看碎片细节。
💎 总结
G1对TLAB未使用空间的管理核心是:
填充Dummy Object → Young GC统一回收 → 牺牲可控空间浪费换取分配速度与GC效率。
这种设计完美适配了**“对象朝生夕死”** 的年轻代特性,通过空间换时间优化高并发场景下的性能。