君子以自强不息。
TLAB
TLAB 产生的目的就是为了进行内存快速分配。通常来说,JVM 堆是所有线程的共享区域。因此,从 JVM 堆空间分配对象时,必须锁定整个堆,以便不会被其他线程中断和影响。为了解决这个问题,TLAB 试图通过为每个线程分配一个缓冲区来避免和减少使用锁。
在分配线程对象时,从 JVM 堆中分配一个固定大小的内存区域并将其作为线程的私有缓冲区,这个缓冲区称为 TLAB。只有在为每个线程分配 TLAB 缓冲区时才需要锁定整个 JVM 堆。由于 TLAB 是属于线程的,不同的线程不共享 TLAB,当我们尝试分配一个对象时,优先从当前线程的 TLAB 中分配对象,不需要锁,因此达到了快速分配的目的。
TLAB 是 Eden 区域中的一块内存,不同线程的 TLAB 都位于 Eden 区,所有的 TLAB 内存对所有的线程都是可见的,只不过每个线程有一个 TLAB 的数据结构,用于保存待分配内存区间的起始地址(start)和结束地址(end),在分配的时候只在这个区间做分配,从而达到无锁分配,快速分配。
虽然 TLAB 在分配对象空间的时候是无锁分配,但是 TLAB 空间本身在分配的时候还是需要锁的,G1 中使用了 CAS 来并行分配。
快速分配步骤
1、从线程的 TLAB 分配空间,如果成功则返回。
2、不能分配,先尝试分配一个新的 TLAB,再分配对象。
从 TLAB 已分配的缓冲区空间直接分配对象,也称为指针碰撞法分配。在 TLAB 中保存一个 top 指针用于标记当前对象分配的位置。
如果剩余空间(end-top)大于待分配对象的空间(objSize),则直接修改 top=top+ObjSize,相关代码位于 thread->tlab().allocate(size) 中。
如果剩余空间(end-top)小于待分配对象的空间(objSize),则分配新的 TLAB,相关代码位于 allocate_from_tlab_slow() 中。
如何判断 TLAB 满了?
虚拟机内部会维护一个叫做 refill_waste 的值,当请求对象大于 refill_waste 时,会选择在堆中分配,若小于该值,则会废弃当前 TLAB,新建 TLAB 来分配对象。默认值为64,即表示使用约为1/64的 TLAB 空间作为 refill_waste。refill_waste 的初始值为16K,即 TLAB 中还剩(1M-16k=1024-16=1008K)1008K内存时直接分配一个新的,否则尽量使用这个老的 TLAB。
可以通过 TLABRefillWasteFraction 来修改 TLAB 中允许产生这种浪费的比例。
TLAB 大小的影响
如果 TLAB 过小,那么 TLAB 则不能存储更多的对象,所以可能需要不断地重新分配新的 TLAB。但是如果 TLAB 过大,则可能导致内存碎片问题。
JVM 提供了参数 TLABSize 用于控制 TLAB 的大小, TLABSize 默认值是0,JVM 会推断这个值多大更合适。采用的参数为 TLABWasteTargetPercent,用于设置 TLAB 可占用的 Eden 空间的百分比,默认值1%,推断方式为 TLABSize=Eden×2×1%/线程个数(乘以2是因为假设其内存使用服从均匀分布)。