tlab 是 jvm 在 eden 区为每个线程私有分配的内存块,用于避免对象分配时竞争全局堆锁;关闭后所有 new 操作需同步访问 eden,导致线程阻塞、分配变慢、gc 频繁。

TLAB 是什么,为什么不用它会卡在 new 上
TLAB 是 JVM 在 Eden 区为每个线程私有分配的一小块内存,线程新建对象时优先往自己的 TLAB 里写,完全不碰全局堆锁。一旦关闭或大小不合理,所有线程抢 Eden 的指针和锁,new 操作就会频繁进入安全点、触发同步等待——你看到的“小对象创建变慢”“GC 日志里 Allocation Failure 频次异常高”,大概率是 TLAB 没配好。
常见错误现象:Unsafe.allocateInstance 走得快,但普通 new 却卡顿;JFR 或 jstat -gc 显示 EC(Eden Capacity)利用率低,但 EU(Eden Used)涨得慢且不连续;线程 dump 里大量线程阻塞在 SharedHeap::allocate 或类似符号上。
- 默认开启(HotSpot 8u60+),但大小由
-XX:TLABSize、-XX:TLABWasteTargetPercent等隐式控制,不能只靠默认 - 小对象(
,具体看 <code>-XX:MaxTLABSize)才进 TLAB;大对象直接走Eden公共区,必然竞争 - TLAB 用满后会尝试 refill,失败则降级到共享 Eden 分配——这个过程可能触发 minor GC,尤其当 Eden 剩余空间不足时
-XX:+UseTLAB 关了会怎样,哪些场景真要关
关掉 -XX:+UseTLAB 后,所有对象分配都走共享 Eden,相当于把线程间并发分配退化成串行排队。对吞吐敏感的服务(如网关、批处理)几乎必掉性能,延迟毛刺明显增多。
真正需要关的场景极少,仅限:调试 TLAB 行为本身,或极少数 GC 调优中想观察“无局部缓冲下的真实分配模式”。生产环境关它,基本等于主动给自己加锁。
立即学习“Java免费学习笔记(深入)”;
- 某些 JDK 版本(如早期 7u)在 CMS 下关 TLAB 可缓解 promotion failure,但那是历史包袱,现代 G1/ZGC 不适用
-
-XX:-UseTLAB不影响逃逸分析和栈上分配,但会让原本能进 TLAB 的对象全部挤向 Eden 头部,加剧内存碎片 - 关了之后,
ThreadLocal对象的创建不会变快——它只是引用容器,实际对象仍走堆分配路径
怎么调 TLABSize 和 TLABWasteTargetPercent
不要硬设 -XX:TLABSize。TLAB 大小是动态的:JVM 根据线程分配速率、Eden 大小、历史 refill 频次自动调整,初始值只是起点。强行固定反而容易导致浪费或过早 refill。
关键参数其实是 -XX:TLABWasteTargetPercent(默认 1%),它控制每次 refill 前允许浪费多少 TLAB 空间。值太小(如 0.1)→ refill 太勤 → 频繁同步;太大(如 5)→ 单个 TLAB 过大 → Eden 利用率下降、易触发提前 GC。
- 高分配率服务(如实时计算)可适当调高到
2-3,减少 refill 次数 - 对象大小方差大(既有 byte[] 也有 POJO)时,注意
-XX:MinTLABSize(最小值,默认 2KB)别设得太低,否则小对象也频繁 refill - 用
-XX:+PrintTLAB观察日志,重点关注waste字段:若长期 >Target,说明当前策略浪费严重;若refills次数远高于线程数,说明 TLAB 太小或分配不均
G1 和 ZGC 下 TLAB 还重要吗
重要,而且机制更精细。G1 的 TLAB 仍绑定 Eden region,但 refill 时会考虑 region 的可用性;ZGC 的 TLAB 实际是 per-thread 的 colored pointer 缓冲区,虽然不涉及传统锁,但仍有“本地缓冲耗尽→申请新 page”的开销。
区别在于:G1/ZGC 下 TLAB refill 不再直接导致 STW,但频繁 refill 仍增加元数据操作和 memory mapping 压力,间接抬高 pause 时间。
- G1 中,
-XX:G1NewSizePercent影响 Eden 总大小,从而约束 TLAB 上限;盲目增大 Eden 不等于 TLAB 更大,要看线程数和分配节奏 - ZGC 默认启用
-XX:+UseTLAB,且无法关闭(JDK 17+),它的 TLAB 管理藏在ZPageAllocator内部,调优主要靠-XX:ZCollectionInterval和堆大小间接影响 - 无论哪种 GC,TLAB 无法规避大对象(>
MaxTLABSize)的共享分配——所以对象池、复用byte[]、避免临时大数组,比调 TLAB 参数更治本
TLAB 不是开关一开就万事大吉的黑盒,它和你的对象大小分布、线程生命周期、GC 类型深度耦合。最容易被忽略的是:日志里看到 TLAB waste 高,第一反应不该是调参数,而是去查 ObjectSizeInBytes 分布——很多“TLAB 问题”,本质是代码里无意 new 出了一堆 64KB 的 StringBuilder。








