Skip to content
Go back

TLAB——线程本地分配缓冲如何让对象分配几乎无锁

TLAB:为什么 new Object() 这么快?

一句话结论(30s)

TLAB 的本质是 JVM 在 Eden 区为每个线程预分配的一块私有内存(默认占 Eden 的 1%),把共享堆上的并发分配转化为线程内私有分配。因为 TLAB 内分配是纯指针碰撞、单条 CPU 指令完成,无需 CAS 也无需锁,所以多线程 new 对象几乎无锁;只有 TLAB 用完时才 CAS 争抢新 TLAB。权衡是它以牺牲少量 Eden 碎片为代价换取极致的分配速度,而且只加速分配、不解决回收。

核心原理(2min)

对象分配路径是三道防线:TLAB 内指针碰撞(极速)→ TLAB 用完时 CAS 从共享空闲区抢新 TLAB → 大对象直接进堆;配合逃逸分析,NoEscape 的局部对象可进一步栈上分配(方法返回自动清理),这是第二道防线,让部分对象根本不用进堆。JVM 会按各线程分配速率动态调整 TLAB 大小——分配快的变大以减少 CAS 频率、分配慢的变小以减少 Eden 碎片化,可用 -XX:+PrintTLAB 观察每个线程的统计。

底层深入(5-10min)

对象分配的并发挑战

堆是所有线程共享的。多个线程同时 new 对象 → 并发争抢堆内存 → 需要 CAS 或锁保证分配正确性 → 并发分配成为瓶颈。

想一想:为什么共享堆上并发 new 会成为瓶颈?因为多个线程抢同一块内存,必须靠 CAS 或锁保证”这块地址只被分配一次”,竞争一激烈,锁就成了吞吐的天花板。

TLAB 的解决

JVM 在 Eden 区为每个线程预分配一块私有内存——TLAB(Thread Local Allocation Buffer,默认占 Eden 的 1%):

Eden 区:
[TLAB-Thread1][TLAB-Thread2][...][共享空闲区]

每个线程在自己的 TLAB 内分配对象:
  ptr += object_size;  // 指针碰撞,单条指令!
  if (ptr > TLAB_end)  // TLAB 用完
      → CAS 从共享区抢新的 TLAB

TLAB 内的分配是纯指针碰撞——不需要 CAS,不需要锁,一条 CPU 指令完成。 只有 TLAB 用完时才需要 CAS 争抢新的 TLAB。

想一想:为什么 TLAB 内分配不需要 CAS?因为这块内存是线程私有的,只有本线程会写它的 top 指针,根本没有竞争,自然不需要同步;把”全局竞争”变成”私有独占”,CAS 次数就从”每个对象一次”降到”每块 TLAB 一次”。

TLAB vs 栈上分配

TLAB栈上分配
位置Eden 内私有块线程栈帧内
触发条件所有堆分配逃逸分析判定 NoEscape
对象回收GC方法返回自动清理
适用大部分新对象不逃逸的局部对象

TLAB 是一道防线(让堆分配变快),栈上分配是二道防线(让部分对象不进堆)。

TLAB 的动态调整

JVM 根据线程的分配速率动态调整 TLAB 大小:

想一想:为什么分配快的线程 TLAB 反而要”变大”?因为 TLAB 越大、用完越慢,抢新 TLAB 的 CAS 频率越低,分配快的线程就更少被 CAS 打断;分配慢的线程 TLAB 小,避免大块内存用不满、在 Eden 里留下碎片。

-XX:+PrintTLAB 可以看到每个线程的 TLAB 统计。

总结

TLAB = 线程私有的 Eden 碎片。对象分配路径:TLAB内指针碰撞(极速) → TLAB用完CAS抢新TLAB → 大对象直接进堆。配合逃逸分析的栈上分配,JVM 的对象分配已经是高度优化的无锁流程。

章末提问

追问 1:TLAB 为什么能让对象分配”几乎无锁”?

回答思路:结论先行——因为它把共享堆上的并发分配转成线程内私有分配。因为 JVM 给每个线程在 Eden 预分配一块私有 TLAB,本线程在 TLAB 内用指针碰撞(单条指令后移 top 指针)分配,没有竞争所以无需 CAS/锁;只有 TLAB 用完才 CAS 抢新 TLAB,频率远低于”每个对象都 CAS”。

追问 2:TLAB 和栈上分配(逃逸分析)是一回事吗?

回答思路:结论先行——不是,它们是两道不同防线。因为 TLAB 只是”让堆分配变快”,对象最终还是进堆、由 GC 回收;栈上分配针对 NoEscape 对象,直接在栈帧里分配、方法返回自动清理,根本不用进堆。TLAB 覆盖几乎所有新对象,栈上分配只覆盖不逃逸的局部对象。

追问 3:TLAB 有什么代价?

回答思路:结论先行——牺牲少量 Eden 碎片,且只加速分配、不解决回收。因为每个线程的 TLAB 可能用不满就换新的,块与块之间会留碎片;同时 TLAB 只影响分配路径,对象回收仍靠 GC,所以它解决的是”分配瓶颈”而非”回收瓶颈”。


Share this post on:

Next Post
MySQL插入意向锁——间隙锁的并发优化变体