TLAB 是 JVM 堆中为每个线程预先分配的一小块私有内存区域,主要作用是减少多线程场景下对象分配时的锁竞争,提升分配效率。当应用中存在大量频繁创建的短命大对象时,TLAB 很容易出现分配失败的情况,这种失败会带来额外的性能开销,需要针对性分析其代价。

TLAB 分配失败的基本逻辑
TLAB 的大小是动态调整的,默认情况下会根据线程的历史分配行为计算合适的尺寸。当线程需要分配对象时,首先会尝试在自身的 TLAB 中分配,如果 TLAB 剩余空间不足以容纳当前对象,就会触发 TLAB 分配失败,此时 JVM 会执行以下操作:
- 如果当前 TLAB 还未装满,会先尝试慢分配,在 TLAB 剩余空间中尝试分配
- 慢分配失败后,会废弃当前 TLAB,重新申请一个新的 TLAB 来分配对象
- 如果新申请的 TLAB 仍然无法容纳该对象,就会直接在堆的共享区域(Eden 区)分配,这个过程需要加锁保证线程安全
对于短命大对象来说,其大小往往接近甚至超过 TLAB 的默认大小,这就导致每次分配都大概率触发 TLAB 分配失败,频繁走慢分配或者直接共享区域分配的流程,带来额外的性能损耗。
分析分配失败代价的准备步骤
要准确分析 TLAB 分配失败的代价,首先需要开启 JVM 的相关日志输出,获取 TLAB 的运行数据。可以通过添加以下 JVM 参数来开启 TLAB 相关的详细日志:
-XX:+PrintTLAB -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log
这些参数会在 GC 日志中输出每个线程的 TLAB 分配情况,包括 TLAB 的大小、分配次数、失败次数、慢分配次数等核心指标。如果是 JDK 11 及以上版本,也可以使用统一的日志参数:
-Xlog:tlab=debug:file=tlab.log -Xlog:gc*:file=gc.log
核心指标与代价计算方式
从输出的 TLAB 日志中,我们可以提取以下关键指标来评估分配失败的代价:
| 指标名称 | 含义 | 与代价的关联 |
|---|---|---|
| TLAB 分配失败次数 | 线程在 TLAB 中分配对象失败的总次数 | 次数越多,说明大对象分配越频繁,额外开销越高 |
| 慢分配次数 | TLAB 剩余空间不足但尝试慢分配的次数 | 慢分配需要额外的空间检查逻辑,会消耗 CPU 资源 |
| 共享区域分配次数 | TLAB 无法容纳对象后直接在 Eden 区分配的次数 | 每次共享区域分配都需要加锁,竞争越激烈开销越大 |
| TLAB 申请/废弃次数 | 线程重新申请和废弃 TLAB 的次数 | 频繁申请废弃 TLAB 会带来额外的内存管理开销 |
分配失败的代价可以从两个维度量化:
CPU 开销维度
每次 TLAB 分配失败都会带来额外的 CPU 消耗,包括慢分配的逻辑判断、TLAB 废弃和申请的内存操作、共享区域分配时的锁竞争开销。可以通过对比开启和关闭 TLAB 的场景下,相同业务逻辑的 CPU 使用率差异,大致估算这部分开销。如果关闭 TLAB 后 CPU 使用率反而下降,说明当前 TLAB 分配失败的代价已经超过了 TLAB 本身带来的收益。
延迟开销维度
TLAB 分配失败的额外逻辑会增加单次对象分配的耗时,对于频繁创建短命大对象的场景,这种延迟会累积,导致业务接口的响应时间变长。可以通过压测工具在固定 QPS 下,对比 TLAB 相关参数调整前后的接口 P99、P999 延迟,评估分配失败带来的延迟代价。
实际分析示例
假设我们有一个应用,会频繁创建大小为 512KB 的短命对象,默认的 TLAB 大小为 256KB,此时几乎每次对象分配都会触发 TLAB 分配失败。我们可以通过以下步骤分析代价:
首先查看 TLAB 日志,发现每个线程的 TLAB 分配失败次数每分钟达到数万次,共享区域分配占比超过 80%,说明大部分对象都走了加锁分配的流程。
然后我们可以通过调整 TLAB 大小来验证代价,添加 JVM 参数:
-XX:TLABSize=1M
调整后再次观察日志,发现 TLAB 分配失败次数下降了 90%,共享区域分配占比降到 5% 以下。此时对比调整前后的压测结果,发现接口平均响应时间从 20ms 降到了 12ms,CPU 使用率从 75% 降到了 60%,说明之前的 TLAB 分配失败带来的性能代价非常明显。
常见优化方向
如果分析发现 TLAB 处理频繁短命大对象的分配失败代价较高,可以尝试以下优化方案:
- 适当调大 TLAB 的大小,让大对象能够尽可能在 TLAB 中分配,减少失败次数
- 如果大对象的大小比较固定,可以设置 TLAB 的大小为大对象大小的整数倍,提升空间利用率
- 对于确实无法在 TLAB 中分配的超大对象,可以考虑优化对象结构,拆分大对象为多个小对象,减少单个对象的大小
- 如果应用线程数较多,可以适当调整 TLAB 的占用 Eden 区的比例,避免 TLAB 占用过多空间导致 GC 频繁
需要注意的是,TLAB 大小不是越大越好,过大的 TLAB 会导致 Eden 区空间被大量占用,反而增加 GC 的频率,需要结合实际的日志数据找到平衡点。
分析 TLAB 分配失败代价时,一定要结合应用的真实业务场景,不能只看单一指标,需要综合 CPU、延迟、GC 频率等多维度数据做判断,才能得出准确的结论。