tlab能绕过eden区top指针竞争,因其为每个线程在eden区内预分配私有缓冲区,线程仅操作本地top/end指针,避免共享cas;对象≤剩余空间则指针碰撞分配,无锁;超限才同步refill或走共享分配。

TLAB 为什么能绕过 Eden 区的 top 指针竞争
不启用 TLAB 时,100 个线程同时 new 对象,都要原子更新 Eden 区唯一的 top 指针——这必须靠 CAS 自旋或锁,失败重试多、延迟高、CPU 白耗。TLAB 把这个共享热点拆成 N 个线程私有子区域,每个线程只动自己的 top 和 end 指针,完全不碰其他线程的数据,自然无锁。
关键点在于:TLAB 是 Eden 的一部分,不是额外内存;它只是把 Eden 划出 N 块互不重叠的连续空间,由 JVM 在线程启动时预分配并维护边界。
- 对象大小 ≤ 当前 TLAB 剩余空间 → 直接 bump
top,无同步 - 对象大小 > 剩余空间 → 触发 TLAB refill,此时才需同步申请新块(频次极低)
- 大对象(如 >
-XX:TLABWasteTargetPercent阈值或超过 TLAB 剩余)直接跳过 TLAB,走共享 Eden 分配
怎么看自己应用是否真在用 TLAB
光看 -XX:+UseTLAB 开启没用,得验证运行时行为。最直接的方式是加参数启动:
-XX:+PrintTLAB -Xlog:gc+ref+phases=debug
日志里会出现类似这样的行:
TLAB: gc thread: 0x00007f8b4c00a800 [0x00007f8b4d000000,0x00007f8b4d020000,0x00007f8b4d040000) -> 131072B waste: 4096B
其中 waste 值高(比如 >10%)说明当前 TLAB 大小不合理,可能频繁 refill 或内部碎片多;refill 次数在 GC 日志中累计过高,也意味着 TLAB 太小。
-
-XX:+ResizeTLAB必须开启(默认已开),否则 TLAB 大小固定,无法适应线程分配节奏变化 - 禁用 TLAB 测试性能影响只能用
-XX:-UseTLAB,但生产环境绝对不要这么干 - 注意
-XX:TLABSize是硬指定,容易导致小线程浪费、大线程频繁 refill,通常让 JVM 自适应更稳
TLAB 和栈上分配不是一回事,但会协同工作
栈上分配(Escape Analysis 后优化)优先级高于 TLAB:如果 JIT 确认对象不会逃逸,就根本不在堆分配,连 TLAB 都不进。但一旦逃逸分析失败(比如对象被 return 出方法、存入 static 字段、作为参数传给未知方法),对象就必须落堆——这时 TLAB 就是第一道加速屏障。
也就是说,TLAB 不是“替代”栈分配,而是“兜底加速”:栈分配失败后,它让堆分配尽可能快。
- 逃逸分析开关:
-XX:+DoEscapeAnalysis(JDK8 默认开,JDK15+ 默认关,需显式打开) - TLAB 不影响逃逸判定逻辑,但会影响最终分配路径:栈上分配 → TLAB → Eden 共享区 → 老年代
- 零初始化可提前到 TLAB refill 阶段完成,后续在该 TLAB 内分配对象省去逐个清零,这也是隐形加速点
TLAB 大小调优最容易踩的坑
很多人一看到 “TLAB 浪费率高” 就立刻调大 -XX:TLABSize,结果反而引发 Eden 提前满、Minor GC 更频繁——因为 TLAB 是从 Eden 扣出来的,单个线程占太大,其他线程没空间,被迫退到共享区甚至触发 GC。
真正合理的做法是观察线程间分配不均衡性:比如一个批处理线程高频创建对象,其余 HTTP 线程几乎不分配。这种场景下,强制统一 TLAB 大小反而有害。
- JVM 动态调整依赖
-XX:+ResizeTLAB和历史分配速率,关闭它等于放弃自适应能力 -
-XX:TLABWasteTargetPercent控制 refill 触发阈值(默认 1%),调太高会导致小对象也绕过 TLAB,失去意义 - TLAB 碎片只存在于 Eden,每次 Minor GC 都整块回收,不累积;所以不必担心“长期内存碎片”,那是老年代的事
PrintTLAB 日志里的 waste 和 refill 次数,比盲目调参可靠得多。










