TLAB:Thread Local Allocation Buffer
TLAB(Thread Local Allocation Buffer,线程本地分配缓冲区)是 JVM 为了优化对象分配性能而在每个线程中预先分配的一块堆内存区域,目的是减少多线程环境下堆内存分配的锁竞争。
TLAB是虚拟机在堆内存的eden划分出来的一块专用空间,是线程专属的。在虚拟机的TLAB功能启动的情况下,在线程初始化时,虚拟机会为每个线程分配一块TLAB空间,只给当前线程使用,这样每个线程都单独拥有一个空间,如果需要分配内存,就在自己的空间上分配,这样就不存在竞争的情况,可以大大提升分配效率。
注意到上面的描述中”线程专属”、”只给当前线程使用”、”每个线程单独拥有”的描述了吗?
所以说,因为有了TLAB技术,堆内存并不是完完全全的线程共享,其eden区域中还是有一部分空间是分配给线程独享的。
这里值得注意的是,我们说TLAB是线程独享的,但是只是在“分配”这个动作上是线程独占的,至于在读取、垃圾回收等动作上都是线程共享的。而且在使用上也没有什么区别。
也就是说,虽然每个线程在初始化时都会去堆内存中申请一块TLAB,并不是说这个TLAB区域的内存其他线程就完全无法访问了,其他线程的读取还是可以的,只不过无法在这个区域中分配内存而已。
并且,在TLAB分配之后,并不影响对象的移动和回收,也就是说,虽然对象刚开始可能通过TLAB分配内存,存放在Eden区,但是还是会被垃圾回收或者被移到Survivor Space、Old Gen等.
参见 本文 第八小节: ✅八、tlab 占用的空间算谁的?堆还是栈?
🧠 一、为什么需要 TLAB?
在没有 TLAB 的情况下,多个线程申请内存时,会争抢堆内存中的 Eden 区,造成频繁的锁竞争,影响性能。
为了让对象分配更加高效,JVM 在每个线程中划出一块小的专属内存区域(TLAB),让线程可以无锁地在其中快速分配对象。
🚀 二、TLAB 的工作原理
每个线程在 JVM 初始化时会分配一个 TLAB。
当线程需要创建对象时,先在自己的 TLAB 中分配内存。
如果 TLAB 剩余空间不足,才会:
向堆申请新的 TLAB;
或者直接从 Eden 区全局分配(此时可能有锁)。
📌 三、TLAB 特点总结 和缺点
虽然在一定程度上,TLAB大大的提升了对象的分配速度,但是TLAB并不是就没有任何问题的。
前面我们说过,因为TLAB内存区域并不是很大,所以,有可能会经常出现不够的情况。在《实战Java虚拟机》中有这样一个例子:
比如一个线程的TLAB空间有100KB,其中已经使用了80KB,当需要再分配一个30KB的对象时,就无法直接在TLAB中分配,遇到这种情况时,有两种处理方案:
直接在堆内存中对该对象进行内存分配。
废弃当前TLAB,重新申请TLAB空间再次进行内存分配。
以上两个方案各有利弊:
如果采用方案1,那么就可能存在着一种极端情况,就是TLAB只剩下1KB,就会导致后续需要分配的大多数对象都需要在堆内存直接分配。
如果采用方案2,也有可能存在频繁废弃TLAB,频繁申请TLAB的情况,而我们知道,虽然在TLAB上分配内存是线程独享的,但是TLAB内存自己从堆中划分出来的过程确实可能存在冲突的,所以,TLAB的分配过程其实也是需要并发控制的。而频繁的TLAB分配就失去了使用TLAB的意义。
为了解决这两个方案存在的问题,虚拟机定义了一个refill_waste的值,这个值可以翻译为“最大浪费空间”。
当请求分配的内存大于refill_waste的时候,会选择在堆内存中分配。若小于refill_waste值,则会废弃当前TLAB,重新创建TLAB进行对象内存分配。
前面的例子中,TLAB总空间100KB,使用了80KB,剩余20KB,如果设置的refill_waste的值为25KB,那么如果新对象的内存大于25KB,则直接堆内存分配,如果小于25KB,则会废弃掉之前的那个TLAB,重新分配一个TLAB空间,给新对象分配内存。
当一个TLAB被填满或者废弃时,原有TLAB中的对象不会被移动或复制到新的TLAB中。在JVM中,一旦对象被分配在堆上,它们通常会保持在原地直到被垃圾回收。所以,当一个TLAB用完时,线程会简单地分配一个新的TLAB,并在新的TLAB上继续对象分配。原有TLAB中的对象将保留在其当前位置,直到它们不再被引用并由垃圾收集器回收。
⚙️ 四、如何开启 / 配置 TLAB(默认是开启的)
-XX:+UseTLAB # 开启 TLAB(默认开启)
-XX:TLABSize # 固定每个 TLAB 的大小(单位字节)
-XX:+PrintTLAB # 打印 TLAB 分配信息
-XX:+ResizeTLAB # 是否允许 JVM 动态调整 TLAB 大小(默认开启)
🔍 五、对象不是都能在 TLAB 中分配吗?
不是,以下几种情况不会使用 TLAB:
TLAB 剩余空间不足
对象太大(超出 TLAB 大小)
TLAB 动态调整未完成,线程刚刚启动
关闭了 TLAB 功能
🧪 六、TLAB 示例日志输出(通过 -XX:+PrintTLAB)
TLAB: start 0x00000007c0000000, size 1024KB, free 64KB, refill-waste 8KB
✅ 七、总结
TLAB 是 JVM 中一项极具实用性与性能提升价值的设计,尤其适合频繁小对象分配的高并发系统场景。
✅八、tlab 占用的空间算谁的?堆还是栈?
💡 TLAB(Thread Local Allocation Buffer)占用的空间属于堆内存(Heap),而不是栈内存(Stack)!
✅ 一句话结论:
TLAB 是线程私有的,但它分配在 Java 堆中,属于 Eden 区的一部分。
📦 一、TLAB 所在的位置?
🧠 二、TLAB 与堆关系图示(简化):
Java 堆(Heap)
├── 新生代(Young Gen)
│ ├── Eden 区 ← TLAB 来自这里
│ ├── Survivor From 区
│ └── Survivor To 区
└── 老年代(Old Gen)
TLAB 是 Eden 区划分给每个线程的一块小内存区域
🔍 三、为什么容易混淆为栈?
因为 TLAB 是 “线程本地”(Thread Local)的,所以很多人误以为它像 ThreadLocal 一样是存在栈中。
但 JVM 中“线程本地” ≠ “放在线程栈”。
TLAB 实际上是线程独占的堆内存片段,它并不位于栈中,只是避免了多线程争用堆。
🧪 四、验证 TLAB 属于堆的方法
你可以使用 jmap -heap <pid>、jstat -gc <pid> 或 -XX:+PrintTLAB 等方式观察:
TLAB大小加起来 ≈ Eden 容量的一部分
Eden 内存减少 → TLAB 分配过多
评论区