G1如何与TLAB一起工作

54 阅读3分钟

G1如何与TLAB一起工作

TLAB在G1上,完全复用了小对象的内存分配流程,在选择region上,region内部使用指针碰撞等细节完全一致。可以认为TLAB是一个固定大小的小对象,G1按照这个大小分配了一个小对象空间,这块空间并没有用于存储一个小对象,而是用户存储多个小对象。

image.png

在G1垃圾收集器中,当线程的TLAB(Thread Local Allocation Buffer)申请的空间未被完全使用时,其处理机制涉及碎片管理、内存回收和GC优化策略。以下是详细流程:


🔍 1. TLAB剩余空间标记为碎片

  • 碎片产生场景
    • 主动废弃:当TLAB剩余空间不足以分配新对象(但大于refill_waste阈值)时,线程会废弃当前TLAB,剩余空间成为碎片。
    • GC触发回收:Young GC发生时,所有TLAB无论是否用完均被废弃,未分配空间转为碎片。
  • 碎片本质:这些空间属于Eden Region的一部分,但无法再被分配使用,形成内存孔隙(Fragmentation)

⚙️ 2. 填充哑元对象(Dummy Object)

为解决碎片对GC扫描效率的影响,G1采用以下步骤:

  1. 填充操作: 在TLAB被废弃或GC触发时,剩余空间会被一个特殊填充对象(Dummy Object) 占据。该对象仅包含对象头(约12-16字节),无实际数据。
  2. GC扫描优化
    • Dummy Object在GC标记阶段被直接识别为“死亡对象”,扫描时跳过其占据的内存区域。
    • 避免无效扫描:若不填充,GC需遍历整个Region检查每个地址是否被使用,极大降低效率。

image.png

🔄 3. 空间回收时机

  • Young GC统一回收: 所有Eden Region(包括含碎片的TLAB区域)在Young GC时被整体回收:
    1. 存活对象复制到Survivor Region。
    2. 碎片空间随整个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效率

这种设计完美适配了**“对象朝生夕死”** 的年轻代特性,通过空间换时间优化高并发场景下的性能。