全称是 Thread Local Allocation Buffer,即线程本地分配缓存,是一个线程专用的内存分配区域。
1、Java对象的内存分配过程如何保证线程安全的?
由于堆是线程之间共享的,若是在并发场景中,两个线程前后把对象的引用指向了同一个内存区域,怎么办?
为了解决这个并发问题,对象的内存分配过程就必须进行同步控制,可是,不管使用哪一种方案(有多是CAS),都会影响内存的分配效率。然而对于 Java 来讲对象的分配是高频操做。
由此 HotSpot 虚拟机采用了这个方案:每一个线程在 Java 堆中预先分配一小块内存,而后在给对象分配内存的时候,直接在本身的这块”私有“内存中进行分配,当这部分用完以后,再分配新的”私有“内存。
这种方案被称之为 TLAB 分配。这部分 buffer 是从堆中划分出来的,可是本地线程独享的。
2、什么是 TLAB
TLAB 是虚拟机在内存的 eden 区划分出来的一块专用空间,是线程专属的。在启用 TLAB 的状况下,当线程被建立时,虚拟机会为每一个线程分配一块 TLAB 空间,只给当前线程使用,这样每一个线程都单独拥有一个空间,若是须要分配内存,就在本身的空间上分配,这样就不存在竞争的状况,能够大大提升分配效率。
因此说,由于有了 TLAB 技术,堆内存并非完彻底全的线程共享,其中 eden 区中仍是有一部分空间是分配给线程独享的。
注意:这里 TLAB 的线程独享是针对于分配动做,至于读取、垃圾回收等工做是线程共享的,并且在使用上也没什么区别。
也就是说,虽然每一个线程在初始化时都会去堆内存中申请一块 TLAB,并非说这个 TLAB 区域的内存其余线程就彻底没法访问了,其余线程的读取仍是能够的,只不过没法在这个区域中分配内存而已。
而且,在 TLAB 分配以后,并不影响对象的移动和回收,也就是说,虽然对象刚开始可能经过 TLAB 分配内存,存放在 Eden 区,可是仍是会被垃圾回收或者被移到 S 区和老年代等。
还有一点须要注意的是,咱们说 TLAB 是在 eden 区分配的,由于 eden 区域自己就不太大,并且 TLAB 空间的内存也很是小,默认状况下仅占有整个 eden 空间的 1%。因此,必然存在一些大对象是没法在 TLAB 直接分配。遇到 TLAB 中没法分配的大对象,对象仍是可能在 eden 区或者老年代等进行分配的,可是这种分配就须要进行同步控制,这也是为何咱们常常说:小的对象比大的对象分配起来更加高效。
3、TLAB 带来的问题
主要问题就是由于 TLAB 空间过小致使的。
好比一个线程的 TLAB 空间有 100KB,其中已经使用了 80KB,当须要再分配一个 30KB 的对象时,就没法直接在 TLAB 中分配,遇到这种状况时有两种处理方案:
- 直接在堆内存中对该对象进行内存分配。
- 废弃当前的 TLAB,从新申请 TLAB 空间再次进行内存分配。
方案 1 的话,若是 TLAB 只剩下 1KB 的空间了,那么后续的大多数对象都须要在堆内存中分配,方案 2 的话,有可能会有频繁的废弃 TLAB 申请 TLAB 的状况。TLAB 内存本身从堆中进行分配时也是须要并发控制的,而频繁的分配 TLAB 就失去了 TLAB 的意义了。
为了解决这个问题,虚拟机定义了一个 refill_waste 的值,这个值能够翻译为”最大浪费空间“。
当 TLAB 剩余空间不足时,
- 若请求分配的内存大于 refill_waste,会选择在堆内存中分配。
- 若请求分配的内存小于 refill_waste,会选择废弃当前的 TLAB,从新建立 TLAB 进行对象内存分配。
前面的例子中,TLAB总空间100KB,使用了80KB,剩余20KB,若是设置的refill_waste的值为25KB,那么若是新对象的内存大于25KB,则直接堆内存分配,若是小于25KB,则会废弃掉以前的那个TLAB,从新分配一个TLAB空间,给新对象分配内存。
4、相关参数
主要有三个参数:
- -XX:+/-UseTLAB 用来控制是否开启 TLAB。
- -XX:TLABWasteTargetPercent 设置 TLAB 空间占用 Eden 区的百分比大小,默认是 1%。
- -XX:-ResizeTLAB 禁用自动调整 TLAB 的大小。默认状况下,TLAB的空间会在运行时不断调整,使系统达到最佳的运行状态。
- -XX:TLABSize 手动指定 TLAB 的大小。
- -XX:TLABRefillWasteFraction 调整 refill_waste 参数,默认是64,表示使用 1/64 的 TALB 空间做为 refill_waste 的值。
- -XX:+PrintTLAB 跟踪 TLAB 的使用状况。