TLAB的补充说明

在HotSpot VM里,TLAB只有在“分配”这个动作上是线程独占的,而在使用/收集意义上都还是让所有线程共享的;使用上的共享并不需要做任何额外检查。

所谓TLAB其实就是这样的一个东西:
(简化伪代码)
struct ThreadLocalAllocBuffer { HeapWord* _start; HeapWord* _top; HeapWord* _end;};

每个线程会从Eden分配一大块空间,比如说100KB,作为自己的TLAB。这个start是TLAB的起始地址,end是TLAB的末尾,然后top是当前的分配指针。显然start <= top < end。在Eden分配空间时,用的是bump-the-pointer方式来分配,但由于Eden是所有Java线程所共享的,在bump pointer的时候必须加锁(或者CAS)才可以保证安全;而当每个线程从Eden分配到一块空间当作TLAB来用之后,在TLAB里分配小块空间同样是bump-the-pointer(上面示意的top指针)则不需要加锁。
当一个Java线程在自己的TLAB中分配到尽头之后,再要分配就会出发一次“TLAB refill”,也就是说之前自己的TLAB就“不管了”(所有权交回给共享的Eden),然后重新从Eden里分配一块空间作为新的TLAB。所谓“不管了”并不是说就让旧TLAB里的对象直接死掉,而是把那块空间的控制权归还给普通的Eden,里面的对象该怎样还是怎样。

通常情况下,在TLAB中分配多次才会填满TLAB、触发TLAB refill,这样使用TLAB分配就比直接从共享部分的Eden分配要均摊(amortized)了同步开销,于是提高了性能。其实很多关注多线程性能的malloc库实现也会使用类似的做法,例如TCMalloc。

到触发GC的时候,无论是minor GC还是full GC,要收集Eden的时候里面的空间无论是属于某个线程的TLAB还是不属于任何TLAB都一视同仁,把Eden当作一个整体来收集里面的对象——把活的对象拷贝到survivor space(或者直接晋升到Old Gen)。在GC结束之后,每个Java线程又会重新从Eden分配自己的TLAB。周而复始。

HotSpot VM目前并没有实现单独收集一个线程的TLAB的GC(这种GC叫做thread-local GC或者缩写为TLGC)。如果实现TLGC,那么TLGC可以看作比minor GC(或者叫Young GC)更轻量的一种GC,只需要暂停当前线程去收集该线程独占的部分的堆,而不需要暂停任何其它Java线程。看似很诱人对不对?想像这样的代码:public class Test { public static Test sharedStatic; public Test sharedInstanceField; public static void foo() { Test localVar = new Test(); // 1 if (sharedStatic == null) { sharedStatic = localVar; // 2 } else { sharedStatic.sharedInstanceField = localVar; // 3 } }}(这个例子纯粹为了示意“独占”与“共享”的概念,请不要吐槽线程安全问题 >_<)这里,我们在 (1) 创建了一个新的Test实例。如题主所说,在启动UseTLAB(默认开启)的时候,这个Test实例会被分配在当前执行Test.foo()的线程的TLAB里。TLAB在执行分配动作的时候要更新top指针,而更新这个指针不需要加任何锁。一个对象能够被多个线程看到其实是一种传递的关系:基本条件:类的静态变量是肯定被所有Java线程所共享的。如果有静态变量是引用类型的,那么这些引用类型的静态变量所指向的对象也肯定可以被所有Java线程所访问到。递归条件:一个被共享的引用所指向的Java对象,其中的引用类型字段所指向的Java对象也能被所有Java线程所访问到。在 (2) 或者 (3) 的地方,我们把指向刚分配出来的Test实例的引用赋值到了一个静态变量或者实例字段上。这种动作就可能导致别的线程可以感知到这个新对象的存在,所以这种动作也叫做“发布”(publish)或者叫做“线程逃逸”(thread escaping)。执行这样的动作在当前的HotSpot VM中没有针对是否线程逃逸而做任何特别的处理。“共享”是很自然发生的事情。如果HotSpot VM要实现前面提到的TLGC的话,那就必须要在线程逃逸发生的时候做一些特殊处理了。所谓特殊处理可以是在发生线程逃逸时触发一次minor GC来把当前TLAB里有被共享变量所引用的对象移动到Eden的共享部分去,这种动作叫做“全局化”(globalization)。也可以有别的做法,例如说在发生线程逃逸时先做些标记而不立即触发全局化,想办法把全局化GC推迟一点做,这样可以更高效一些。全局化GC跟普通的minor GC开销差不多,如果一个线程在期望的触发正常TLGC之前触发了一次或多次全局化GC的话,做TLGC就得不偿失了。正是因为如何高效处理全局化是个很麻烦、需要非常细致地处理的事情,所以HotSpot VM才迟迟没有把这个功能做到主干版本上。

本文来源于网络摘抄

猜你喜欢

转载自blog.csdn.net/yunxing323/article/details/109956462