TLAB调优关键在于监控每线程waste与refill:waste>5%或refill>10/s/线程说明失衡,需启用-XX:+ResizeTLAB并设-XX:TLABWasteTargetPercent=1,结合-XX:+PrintTLAB日志与jstat -gc对齐分析。

TLAB 在突发小对象场景下极易浪费或频繁 refill,直接抬高 GC 频率——关键不是看它“开没开”,而是看每条线程的 waste 和 refill 是否失控。
怎么看 TLAB 实际利用率:别信默认值,盯紧 PrintTLAB 日志
默认 TLAB 大小(Eden 的 ~1%)在突发流量下往往偏小,但光看参数没用。必须加 -XX:+PrintTLAB 跑 3–5 分钟真实请求,观察每秒各线程的 refill 次数和 waste 字段:
-
waste > 5%:说明 TLAB 分配后剩余太多没用完就弃用了,典型是线程分配节奏不稳、对象大小离散度高 -
refill > 10/s/线程:TLAB 太小或对象平均尺寸接近 TLAB 剩余空间,导致频繁同步申请新缓冲区 - 日志里出现大量
slow path allocation:说明很多对象已 fallback 到 Eden 共享区走 CAS 分配,竞争已起
怎么调 TLABSize 才不翻车:用 ResizeTLAB + waste 目标,而非硬编码
固定设 -XX:TLABSize=512k 是懒办法,容易在低峰期浪费内存、高峰时仍 refill。正确姿势是让 JVM 自适应:
- 确保
-XX:+ResizeTLAB开启(JDK8+ 默认 true),它会根据线程近期分配速率动态扩缩 TLAB - 用
-XX:TLABWasteTargetPercent=1把平均浪费压到 1% 以内,比默认 65% 更激进也更稳 - 若服务线程数固定且分配特征明确(如网关每线程固定处理 300KB/秒请求体),可关掉自适应:
-XX:-ResizeTLAB -XX:TLABSize=384k,避免 resize 时的短暂同步抖动
为什么突发小对象会让 TLAB 失效:大对象阈值和填充碎片是隐形推手
TLAB 不是“小对象保险箱”,它的失效常被误判。两个关键机制常被忽略:
- 对象是否进 TLAB,取决于它是否 ≤ 当前 TLAB 剩余空间的一半,不是总大小。比如 TLAB 剩 200KB,一个 120KB 对象就会被拒,直接走 Eden CAS 分配
- 被拒绝的对象不会“等一等”,而是立刻触发 refill 或 fallback;同时 JVM 会在旧 TLAB 尾部塞一个
Filler Object占位,造成隐性碎片,进一步压缩可用空间 - 用
-XX:TLABRefillWasteFraction=64可调高该阈值(默认 64,即 1/64),让更大比例的剩余空间能被利用,减少无谓 refill
GC 频率上升的真正线索:别只盯 Young GC 次数,查 humongous allocation 和晋升量
TLAB 失效导致的 GC 上升,往往藏在细节里:
- 如果
-XX:+PrintGCDetails日志里频繁出现humongous allocation,说明大量对象因超 TLAB 剩余一半而直奔 Eden 头部,加剧 Eden 碎片化,Minor GC 触发更早 - 对比
jstat -gc <pid>中的EC(Eden 容量)和EU(Eden 已用),若 EU 波动剧烈且每次 GC 后 EU 回落很少,大概率是 TLAB refill 不及时 + fallback 导致 Eden 被零散填满 - 注意
YGC次数没变但YGCT(耗时)明显增长:这是 CAS 竞争加剧的信号,不是 GC 算法问题,是 TLAB 没兜住分配压力
TLAB 调优最易被跳过的动作,是把 PrintTLAB 日志和 jstat -gc 输出按线程 ID 对齐看——同一时间点,哪个线程 refill 最猛、waste 最高、对应 GC 事件是否同步发生。这点不落地,所有参数调整都是蒙眼打靶。

















