tlab溢出日志本身不表示错误,但每秒超2–3次refill requested即为高频分配卡点信号;需结合printtlab日志中refills(gc周期内 refill 次数)与waste(浪费字节数)综合判断:refills突增且waste长期偏低(如500–2000字节)表明对象大小集中、tlab利用率高,而waste忽高忽低则提示碎片化分配;同时出现大量slow path或humongous allocation说明对象绕过tlab直走eden cas,拖累吞吐;应联动jstat -gc pid观察ec/eu与线程used累加值匹配度,eu持续>85%且refills高即表明eden快速打满;refills最高线程id需回溯对应业务逻辑(如netty eventloop),并关注parnewgc次数是否同步激增;tlabsize不可硬设,应先启用-xx:+resizetlab压测收敛refills至个位数,再用-xx:tlabwastetargetpercent=1控浪费率,大促场景可锁死大小(如512k)并禁用resize以规避延迟抖动。

TLAB 溢出日志本身不表示错误,但每秒超过 2–3 次的 Refill requested 就是高频分配卡点的明确信号。
怎么看 PrintTLAB 日志里真正的溢出压力
开 -XX:+PrintTLAB 后,日志里不是所有 “refill” 都等价。关键要看两列:refills(本线程本次 GC 周期内 refill 次数)和 waste(浪费字节数)。高频分配线程的典型特征是:refills 突增、waste 却很低(waste 接近 TLAB 总大小,说明它根本没怎么用,TLAB 过大。
- 别只扫一眼“refill”,要按线程 ID 聚合统计 60 秒内
refills总和——压测时单线程每秒 >5 次 refill,基本可判定 TLAB 不匹配分配节奏 -
waste字段单位是字节,不是百分比;若长期稳定在 500–2000 字节,说明对象大小集中、TLAB 利用率高;若忽高忽低(比如某次 12KB,下次 800 字节),大概率是分配了大小不一的对象,导致填充碎片化 - 日志中出现大量
slow path allocation或humongous allocation,说明部分对象已绕过 TLAB 直接走 Eden CAS 分配,这会拖慢整体吞吐,需单独排查对象大小分布
用 jstat + PrintTLAB 定位线程级分配失衡
jstat -gc <pid></pid> 给的是全局视图,必须和 PrintTLAB 日志对齐才能定位到具体线程。重点比对三项:EC(Eden 总容量)、EU(Eden 已用)和 PrintTLAB 中各线程的 used 累加值。若 EU 持续 >85% 且线程 refills 高,说明 Eden 区被快速打满,TLAB refill 频率被迫拉高。
- 执行
jstat -gc <pid> 1000 5</pid>(每秒采样 5 次),观察EC是否稳定;若 EC 波动剧烈(如从 256MB 突降到 128MB),说明 JVM 正在动态 resize TLAB,此时-XX:-ResizeTLAB反而更可控 - 把 PrintTLAB 日志中 top 3 refills 最高的线程 ID 记下来,在应用代码里搜它们对应的业务逻辑(比如 Netty EventLoop 线程、Dubbo provider 线程),往往就是高频 new 对象的热点路径
- 注意:GC 日志里的
ParNewGC次数突增,常和 TLAB refill 高度相关——因为每次 refill 都需短暂锁 Eden,多线程并发 refill 会推高 ParNew 触发频率
TLABSize 设多少才不踩坑
硬写 -XX:TLABSize=512k 是最常见也最危险的做法。TLAB 大小必须和线程实际对象生成速率匹配,而不是拍脑袋定值。真实压测中,一个处理 HTTP 请求的线程,平均每秒分配 300KB 对象,那么 512KB 的 TLAB 是合理的;但如果它峰值能冲到 900KB,就会频繁 refill,反而不如设成 1024KB 并关掉自适应。
- 先开
-XX:+ResizeTLAB(默认 true),跑 10 分钟真实流量,看 PrintTLAB 中各线程的refills是否收敛到个位数;再用-XX:TLABWasteTargetPercent=1把浪费率压到 1% 以内 - 大促或秒杀场景建议锁死:比如抖音实测
-XX:TLABSize=512k -XX:-ResizeTLAB,因 resize 过程中涉及 Eden 指针同步,会产生微秒级延迟抖动,对延迟敏感服务不可接受 - 避免 TLAB 过大导致内存浪费:若线程平均
used长期
大对象分配干扰 TLAB 效果的识别与规避
所谓“大对象”,不是指大于某个固定值,而是指大小超过当前 TLAB 剩余空间的一半。一个 new byte[1024*1024](1MB)对象,在 TLAB 剩余 1.8MB 时仍可进 TLAB;但若剩余只剩 1.2MB,它就会被判定为“humongous”,直接走 Eden CAS 分配——这正是你看到分配延迟毛刺的根源。
- 用
-XX:+PrintGCDetails查日志里是否频繁出现humongous allocation,配合 Async-Profiler 的alloc采样,确认哪些类实例或数组占用了大块内存 - 不要手动拆分大数组来“适配”TLAB,比如把 1MB 数组拆成 10 个 100KB;这会导致更多 TLAB refill 和填充对象(Filler Object)碎片,实际吞吐更低
- 真正该做的,是用
-XX:PretenureSizeThreshold把确定的大对象(如缓存中的大 Value)直接导向老年代,避开 Eden 和 TLAB 的一切争抢
TLAB 调优最易被忽略的一点:它永远排在逃逸分析之后。即使你把 TLAB 调得再完美,只要对象能栈上分配,它根本就不会碰 TLAB。所以压测前务必确认 -XX:+DoEscapeAnalysis 已开,且 JIT 已完成足够预热——否则你优化的是一条根本不会走的路径。










