tlab能绕过锁竞争,是因为它为每个线程在eden区划分私有缓冲区,小对象分配仅需本地指针碰撞(bump-pointer),无锁、无cas、无同步;仅tlab耗尽时 refill 才需一次cas更新全局指针。

TLAB 为什么能绕过锁竞争
堆是所有线程共享的,直接在 Eden 区分配对象必须保证指针(eden_top)更新的原子性。HotSpot 默认用 CAS + 自旋重试实现,但高并发下大量线程争抢同一地址,失败率上升、延迟毛刺明显。
TLAB 的本质是把 Eden 切成若干份“线程私有小块”,每个线程只操作自己的 tlab_top,分配就是简单的指针碰撞(bump-the-pointer),完全不涉及任何同步原语。
- 没有锁、没有 CAS、没有重试 → 单次分配耗时从纳秒级降到亚纳秒级
- 即使 100 个线程同时分配,只要各自 TLAB 有空间,就互不干扰
- 只有 refill(申请新 TLAB)时才需短暂进入共享 Eden 区,走一次 CAS,这是唯一需要同步的点
TLAB 如何降低内存碎片和 GC 压力
TLAB 不是额外内存池,它始终从 Eden 中划出连续区域。它的存在反而让 Eden 的使用更“规整”:
- 小对象集中在各线程 TLAB 内部分配,天然形成局部性,减少跨 region 跳跃
- TLAB 用尽后 refill 是批量申请(比如一次拿 256KB),比每次分配都零散请求更高效
- 如果 TLAB 太小(如
-XX:TLABSize=4k),refill 频繁 → 共享 Eden 区 CAS 次数飙升 →slow_allocations上升,GC 日志里能看到 “TLAB waste” 却不触发回收 - 如果 TLAB 过大(如设为 Eden 的 50%),单个线程可能长期占着大块 Eden 空间却不填满,造成隐性浪费,提前触发 Young GC
TLAB 在不同 GC 下表现差异明显
TLAB 本身是 JVM 层机制,但它的实际效果高度依赖 GC 对 Eden 的管理能力:
-
G1 GC:每个线程在 Eden Region 中划分 TLAB,支持动态 resize 和精确 waste 统计;-XX:+PrintTLAB可直接看到 refill 成功率和浪费率 -
Parallel GC:TLAB 利用率最高,refill 极其激进,但日志简略,需结合-XX:+PrintGCDetails推断是否因碎片导致 fallback -
ZGC / Shenandoah:并发移动阶段 Eden 空间被 GC 线程隐式占用,TLAB refill 容易排队降级;此时refill_waste持续偏高,往往反映的是 GC 并发压力,而非 TLAB 配置不当
TLAB 不是万能的:哪些对象根本不会进 TLAB
TLAB 只服务“小对象”的快速路径,以下情况会直接跳过 TLAB,走共享 Eden 或老年代:
- 对象大小超过
-XX:TLABWasteTargetPercent控制的阈值(默认约 1% Eden),或大于当前 TLAB 剩余空间 → 直接在 Eden 分配(仍属新生代,但需 CAS) - 对象太大,Eden 都放不下(比如 2MB 数组),JVM 会直接分配到老年代,绕过整个新生代流程
- 逃逸分析判定对象可栈上分配(
-XX:+DoEscapeAnalysis开启时),连堆都不进,自然不涉及 TLAB - 显式禁用:
-XX:-UseTLAB(极少数调试场景)
真正影响性能的,往往不是“TLAB 是否开启”,而是 Eden 是否足够连续、refill 是否频繁失败、以及对象大小分布是否匹配当前 TLAB 策略——这些细节藏在 GC 日志的 TLAB 行和 slow_allocations 计数里,而不是参数列表中。











