refill_waste_limit是jvm动态计算的碎片容忍阈值,非固定值,初始≈tlab大小/64,后依线程分配节奏浮动;其作用是在不触发refill前提下主动放弃小块剩余空间,避免cas竞争,本质是碎片容忍与延迟的实时权衡。

TLAB 的“浪费上限”(refill_waste_limit)不是为了防止浪费本身,而是为了在**不触发 refill 的前提下,理性放弃一小块剩余空间,避免因强求填满而引发更昂贵的同步竞争**。它本质是 JVM 在碎片容忍度与分配延迟之间做的实时权衡。
浪费上限怎么算出来的
这个阈值不是固定值,而是动态计算的:
- 初始值 ≈ 当前线程 TLAB 大小 / TLABRefillWasteFraction(默认 64,即最多允许浪费约 1.56%)
- 但 JVM 会持续观察该线程近期 refill 行为:如果某次 refill 后很快又用尽,说明上次浪费太多、TLAB 偏小;反之若长期剩大量空间才 refill,说明浪费偏少、TLAB 可能偏大
- 因此实际 refill_waste_limit = max(当前剩余空间 × 某衰减因子, MinTLABSize),它随分配节奏浮动,不是静态配置项
它如何影响碎片和 refill 决策
当一个对象要分配时,JVM 判断逻辑如下:
- 若 对象大小 ≤ 剩余空间 → 直接快速分配(无浪费)
- 若 剩余空间 > refill_waste_limit 且 对象大小 > 剩余空间 → 放弃当前 TLAB 剩余空间,在 Eden 区直接分配(称 “waste allocation”,走慢路径)
- 若 剩余空间 ≤ refill_waste_limit → 触发 refill:尝试申请新 TLAB(可能成功,也可能失败降级)
关键点在于:第二条路径看似“浪费”,实则是主动牺牲几 KB 空间,换取完全避开 CAS 竞争;第三条路径看似“节约”,却把压力转嫁给 Eden 分配指针——高并发下多个线程同时 refill,极易卡在 CAS 重试上,造成线程停顿和内存不连续(即你看到的碎片化)。
怎么看出它正在起作用
开启 -XX:+PrintTLAB 后,重点关注每秒日志中的三个字段:
- waste:本秒因触发 waste allocation 而丢弃的字节数(非 refill 失败导致)
- refills:本秒成功获取新 TLAB 的次数
- slow_alloc:本秒走 Eden 同步分配的对象数(含 waste + humongous + refill 失败降级)
典型健康信号是:waste 占 slow_alloc 的 60% 以上,且 refills 稳定在个位数/秒。这说明 JVM 正在用可控的小浪费,把大部分分配稳稳留在 TLAB 快路径上;反之,若 refills 高、waste 低,反而说明它正被逼着高频抢锁,碎片已在暗中堆积。
调优时不碰这个值,而要看它背后的行为模式
你不需要、也不应该手动设置 refill_waste_limit(它没有对应 JVM 参数)。真正要干预的是它的输入条件:
- 若 waste 过高(>10%)且 refills 很低 → 说明 TLAB 太大,线程分配不均,启用 -XX:+ResizeTLAB 让 JVM 自动缩小
- 若 refills 过高(>3次/秒)且 waste 很低(-XX:TLABWasteTargetPercent(提高目标利用率)
- 若 waste 忽高忽低、refills 也抖动 → 提示业务请求对象体积分布极不均匀,需结合链路追踪定位哪类请求产生了“临界尺寸”对象(比如刚好比当前 TLAB 剩余多 1 字节的 buffer)










