聊聊Java中的TLAB

一、TLAB介绍

TLAB(Thread Local Allocation Buffer)是在Hotspot1.6引入的新技术,目的是提升在堆上创建对象的性能。

如果一个对象被创建到堆上时,需要在堆上申请指定大小的内存供新创建的对象使用,在这个过程中,堆会通过加锁或指针碰撞的方式防止同一块被重复申请,在JVM中,内存分配是一个非常频繁的动作,而给堆加锁或者校验碰撞指针的方式必定会影响内存创建效率,TLAB的出现就是为了优化这个问题。

二、TLAB细节

TLAB是线程的一块私有内存,这块内存在堆中,可以通过JVM参数-XX:+UseTLAB开启。

  • 在线程启动的时候会在堆中为其申请一块指定大小的内存,这块内存只给当前线程使用,属于线程私有的,如果线程需要为线程内的对象分配内存,就再自己的空间上分配,这样就不存在内存竞争的情况了,大大的提升了分配效率;

    image-20200430000859329

    1
    2
    3
    4
    5
    void initialize_tlab() {
    if (UseTLAB) {
    tlab().initialize();
    }
    }
  • 当TLAB空间容量不足时,就新申请一个TLAB,原来的那个TLAB区里的对象还维持现状,因为对象只能感知到自己在Eden区。

  • TLAB空间的内存非常小,默认大小仅有Eden区的1%,也可以通过JVM参数-XX:TLABWasteTargetPercent设置TLAB空间占Eden空间的百分比大小,一般用默认的就可以。

    1
    2
    3
    4
    5
    6
    7
    8
    # 开启TLAB
    -XX:+UseTLAB

    # 关闭TLAB
    -XX:-UseTLAB

    # 设置每个TLAB区域占Eden区的大小比例
    -XX:TLABWasteTargetPercent

三、TLAB规则

  1. 我们下载openjdk,到查看文件hotspot/src/share/vm/memory/threadLocalAllocBuffer.hpp,这是TLAB的源码实现,看一下类中的属性:

    image-20200429221957003

    从类中我们看到四个HeapWord实例,对于我们来说只需要关心start、top和end,我们结合和源码分析一下每个变量的用途。

    • 根据它们的注释我们可以知道start是TLAB的地址,end是申请的TLAB空间的尾部,也就是通过start和end就可以标识出这个TLAB所管理的区域,防止其他线程再来分配这块空间。

    • top是归属线程最后一次申请空间的尾位置,当top撞上end的时候就表示这个TLAB的空间用完了,这时会申请一个新的TLAB。

  2. 每一个TLAB空间大小都是固定的,默认的是Eden区大小的的1%,既然大小是固定的,那么肯定会出现空间浪费的情况,比如TLAB大小是100kb,已经被使用了90kb,此时有一个12kb的对象来申请空间,但是TLAB的剩余空间已经不足以分配给这个对象了,此时怎么办?是新申请一个TLAB,还是直接分配到Eden区?在设计TLAB的时候就已经考虑到这种情况了,使用变量refill_waste_limit来控制一个TLAB允许被浪费的空间大小。

    • 如果refill_waste_limit的值是5kb的话,那么一个TLAB允许浪费的最大空间就是5kb,但是上述情况下TLAB还剩10kb的空间,不满足浪费条件,那么这个TLAB就不能被遗弃,还需要继续使用,所以不能申请新的TLAB,那么这个12kb的对象就只能被分配到Eden区;

    • 如果refill_waste_limit的值是10kb,那么上述情况已经满足了空间可浪费的大小限制,此时就会直接遗弃当前的TLAB,重新申请一个新的来存放申请对象;

    • 如果对象需要的空间特别大,超过了整个TLAB的大小,那么就会被直接放到Eden区。

  3. TLAB是允许浪费一部分空间的,这会导致在大量TLAB都浪费了部分空间的时候Eden区空间不连续,影响整个Eden区的使用。

image-20200501225512825

四、总结

基本上TLAB介绍到这里就可以了,再深入一点的话就要到C++代码层面了。

image-20200429003839422