JVM 的 TLAB(Thread-Local Allocation Buffer)是什么?

Sherwin.Wei Lv8

JVM 的 TLAB(Thread-Local Allocation Buffer)是什么?

回答重点

TLAB(Thread-Local Allocation Buffer)是 JVM 中为每个线程分配的一小块堆内存,用于加速对象的分配操作。每个线程都有自己的 TLAB,,大大加速了内存分配的同时避免了多线程竞争共享堆内存时的同步开销。

工作原理

  • 每个线程在执行过程中优先从自己的 TLAB 中分配内存。
  • 当 TLAB 中的内存耗尽时,线程会重新向 Eden 区申请一个新的 TLAB,或者直接从 Eden 区分配内存。
  • 对象超过一定大小时(大对象),不会在 TLAB 中分配,而是直接在 Eden 区进行分配。

扩展知识

深入理解

这个得从内存申请说起。

一般而言生成对象需要向堆中的新生代申请内存空间,而堆又是全局共享的,像新生代内存又是规整的,是通过一个指针来划分的。

68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f79657373696d6964612f63646e5f696d6167652f696d672f32303232303132333136333431332e706e67.png

内存是紧凑的,新对象创建指针就右移对象大小 size 即可,这叫指针加法(bump [up] the pointer)。

可想而知如果多个线程都在分配对象,那么这个指针就会成为热点资源,需要互斥那分配的效率就低了。

于是搞了个 TLAB(Thread Local Allocation Buffer),为一个线程分配的内存申请区域。

这个区域只允许这一个线程申请分配对象,允许所有线程访问这块内存区域。

TLAB 的思想其实很简单,就是划一块区域给一个线程,这样每个线程只需要在自己的那亩地申请对象内存,不需要争抢热点指针。

当这块内存用完了之后再去申请即可。

这种思想其实很常见,比如分布式发号器,每次不会一个一个号的取,会取一批号,用完之后再去申请一批。

68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f79657373696d6964612f63646e5f696d6167652f696d672f32303232303132333136333531312e706e67.png

可以看到每个线程有自己的一块内存分配区域,短一点的箭头代表 TLAB 内部的分配指针。

如果这块区域用完了再去申请即可。

不过每次申请的大小不固定,会根据该线程启动到现在的历史信息来调整,比如这个线程一直在分配内存那么 TLAB 就大一些,如果这个线程基本上不会申请分配内存那 TLAB 就小一些。

还有 TLAB 会浪费空间,我们来看下这个图。

68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f79657373696d6964612f63646e5f696d6167652f696d672f32303232303132333136333532382e706e67.png

可以看到 TLAB 内部只剩一格大小,申请的对象需要两格,这时候需要再申请一块 TLAB ,之前的那一格就浪费了。

在 HotSpot 中会生成一个填充对象来填满这一块,因为堆需要线性遍历,遍历的流程是通过对象头得知对象的大小,然后跳过这个大小就能找到下一个对象,所以不能有空洞。

当然也可以通过空闲链表等外部记录方式来实现遍历。

还有 TLAB 只能分配小对象,大的对象还是需要在共享的 eden 区分配。

所以总的来说 TLAB 是为了避免对象分配时的竞争而设计的。

启用和配置

TLAB 默认是开启的,但可以通过 JVM 参数来配置或禁用:

  • -XX:+UseTLAB:启用 TLAB。
  • -XX:-UseTLAB:禁用 TLAB。
  • -XX:TLABSize:设置每个 TLAB 的初始大小。

TLAB 与大对象分配

TLAB 并不是适用于所有对象的分配,通常只有小对象会分配到 TLAB 中。如果对象超过一定大小(通常为几 KB),则直接在 Eden 区中分配。这是因为大对象分配在小块 TLAB 中效率较低,直接在 Eden 区分配会更合适。

Comments