Java堆作为所有线程共享的内存区域,在并发环境下面临着激烈的对象分配竞争。为了解决多线程同时创建对象时的性能瓶颈,HotSpot虚拟机引入了线程本地分配缓冲,也就是TLAB。它本质是一块线程私有的内存空间,位于新生代的Eden区中,每个线程在运行时都会绑定自己的TLAB,用来完成绝大多数小对象的分配工作。

TLAB的底层设计原理
在没有TLAB的传统分配模式中,多个线程要从同一个堆里申请空间,必须借助全局锁或者CAS操作来移动分配指针。一旦线程数变多,指针更新的冲突就会呈指数级上升,大量CPU时间消耗在重试和等待上。TLAB的思路是化整为零,在Eden区给每个线程预先划出一小块区域,线程只在这个区域内部做分配,不需要和其他线程协商。
从实现角度看,TLAB的结构里维护了起始地址、当前空闲指针和结束地址。每次新建对象时,线程只需将空闲指针加上对象大小,若没有越过结束地址就分配成功,这仅仅是一条本地内存读写的指令。只有当剩余空间不够时,线程才会进入慢速路径,向虚拟机申请新的TLAB或者直接在共享Eden区分配。由于绝大多数对象体积较小,一次TLAB足以容纳成百上千个对象,因此全局竞争被极大削弱。
需要注意的是,TLAB虽然属于某个线程,但它仍然处在Java堆的Eden区域内,所以里面的对象对其它线程是可见的,只是分配动作被限制在线程内部。当发生垃圾回收时,TLAB会和普通Eden对象一起被扫描,并不会因为私有属性而逃脱回收规则。另外,TLAB的大小不是固定不变的,JVM会根据线程历史分配速率动态调节,以平衡空间浪费和分配效率。
TLAB的分配流程与关键参数
当线程启动后首次接触对象分配,会触发一次TLAB初始化,虚拟机按照TLABSize或自适应策略计算初始容量。后续分配遵循快速路径:先比较所需大小与剩余空间,满足就移动指针返回地址。若不满足,则调用allocate_new_tlab慢速方法,此时可能需要获取全局锁,从Eden区切割新缓冲,同时把旧TLAB的尾部浪费空间计入统计。
在参数层面,-XX:+UseTLAB用于开启或关闭该特性,默认开启;-XX:TLABSize可强制指定初始大小;-XX:TLABWasteTargetPercent控制期望的浪费比例,影响动态扩容缩容。还有一个容易被忽略的TLABRefillWasteFraction,它决定当单个对象超过TLAB剩余空间多少比例时,直接走共享分配而不浪费整个缓冲。理解这些参数对排查频繁refill导致的停顿很有帮助。
下面是一段模拟TLAB快速分配的伪代码,帮助直观理解指针移动逻辑:
// 模拟线程本地分配缓冲的快速分配逻辑
class TLAB {
long start;
long end;
long current;
Object alloc(int size) {
// 快速路径:本地指针加法
if (current + size <= end) {
long addr = current;
current += size;
return Object.fromAddress(addr);
}
// 慢速路径:申请新TLAB
return refillAndAlloc(size);
}
Object refillAndAlloc(int size) {
// 此处需要全局同步,从Eden区获取新缓冲
TLAB newTlab = heap.requestTLAB();
this.start = newTlab.start;
this.end = newTlab.end;
this.current = newTlab.start + size;
return Object.fromAddress(newTlab.start);
}
}
TLAB带来的性能优势与潜在问题
最直观的收益是对象分配延迟的稳定。在压测环境中,开启TLAB后多线程创建小对象的吞吐可提升数倍,因为CAS失败率从每线程每对象一次降到每TLAB一次。对于电商、网关这类高频new对象的业务,这种优化直接反映为更低的RT和更平稳的GC频率。同时,由于分配不抢占共享指针,CPU缓存局部性也更好,减少了伪共享带来的缓存行失效。
但TLAB并非没有代价。首先是内存碎片,每个线程的缓冲末尾往往残留无法利用的空间,若线程很多且对象大小不均,Eden区的有效利用率会下降。其次是线程退出时的缓冲回收,若某个线程突然结束,它占用的TLAB在下次GC前都不能被别的线程使用,相当于临时冻结了一块内存。此外,当业务出现偶发大对象,会频繁触发refill,反而增加锁竞争,此时需要结合TLABWasteTargetPercent调优。
实践中可通过-XX:+PrintTLAB观测每个线程的分配量、 refill次数和浪费字节。如果发现refill频率过高,可适当调大初始TLABSize;如果浪费比例超标,则应检查是否存在大量临界大小的对象。总之,TLAB是JVM在并发分配上做的巧妙折中,理解其机制能让我们在性能调优时有的放矢,而不是盲目调整堆大小。