TLAB默认开启但需调优:高并发下未调优会导致refill频繁、CAS争抢;应结合PrintTLAB日志与jstat监控,动态调整TLABSize及waste率,大对象按剩余空间一半阈值直接走Eden,逃逸分析优先于TLAB。

TLAB到底开不开?默认就开着,但高并发下必须调
TLAB(ThreadLocalAllocationBuffer)在JDK 6u26之后默认开启(-XX:+UseTLAB),但“开着”不等于“配得对”。在QPS过万的交易、广告或网关服务里,未调优的TLAB会频繁触发refill,导致CAS争抢上浮、分配延迟抖动明显——这不是理论风险,是压测时PrintTLAB日志里一眼能看见的“Refill requested”刷屏。
- 默认TLAB大小由JVM自适应计算(通常Eden的1%左右),小而碎,线程多时极易耗尽
- 关闭TLAB(
-XX:-UseTLAB)后,所有分配走共享Eden+CAS,吞吐直接掉30%~50%,别试 - 关键动作:用
-XX:+PrintTLAB跑5分钟真实流量,看日志里每秒refill次数和平均浪费率(waste字段)
怎么设TLABSize?别硬写固定值,先看线程分配压力
直接设-XX:TLABSize=512k看似爽快,但若单线程每秒只分配200KB对象,反而造成内存浪费;若单线程峰值要分配800KB,又得频繁refill。TLAB大小必须匹配线程的实际分配节奏。
- 观察指标:用
jstat -gc <pid></pid>看EC(Eden容量)和EU(Eden已用),再结合PrintTLAB中各线程的refills和waste - 推荐策略:先开
-XX:+ResizeTLAB(默认true),让JVM根据线程分配速率动态扩缩;再用-XX:TLABWasteTargetPercent=1把浪费控制在1%以内 - 大促场景可锁死:如抖音秒杀实测
-XX:TLABSize=512k -XX:-ResizeTLAB比自适应更稳,因避免了resize过程中的短暂同步开销
大对象绕过TLAB?不是“绕过”,是根本没资格进
TLAB只收“小对象”。所谓大对象(Humongous Object),指大小超过TLAB剩余空间一半的对象——注意,不是超过TLAB总大小,而是“当前剩余”的一半。这类对象从不尝试TLAB分配,直接走Eden区共享路径,用CAS抢。
- 典型踩坑:以为
new byte[1024*1024](1MB)会被TLAB收,结果发现它总在Eden区头部分配,还引发ParNewGC波动——查PrintGCDetails就能确认是否为“humongous allocation” - 阈值可控:
-XX:TLABMaxRefillWasteFraction影响判断逻辑,但更直接的是监控对象大小分布,用Async-Profiler采样堆分配热点 - 不要强行拆分大数组:比如把1MB数组拆成10个100KB,可能引发更多TLAB refill和填充对象(
Filler Object)碎片
TLAB和逃逸分析冲突吗?完全不冲突,但优先级有严格顺序
栈上分配(逃逸分析成功)永远优先于TLAB分配。只要JIT判定对象不会逃逸,哪怕你关了TLAB,它也照样不进堆;反之,只要逃逸了(比如被放入static集合、作为返回值传出),哪怕开了TLAB,也必然走堆分配路径。
- 验证方式:加
-XX:+PrintEscapeAnalysis -XX:+DoEscapeAnalysis,看JIT日志里有没有allocated on stack - 常见误判:方法内创建但被
return出去的对象,或被synchronized块锁住的对象,基本逃逸失败 - TLAB只是“堆内加速器”,它不管逃逸与否;逃逸分析失败后的堆分配,才轮到TLAB接棒
PrintTLAB输出——那等于开着仪表盘黑屏开车。

















