TLAB(Thread Local Allocation Buffer)
线程本地分配缓存,这是一个线程独享的内存分配区域。
特点:
- TLAB解决了:直接在线程共享堆上安全分配带来的线程同步性能消耗问题(解决了指针碰撞)。
- TLAB内存空间位于Eden区。
- 默认TLAB大小为占用Eden Space的1%。
开启TLAB的参数:
-XX:+UseTLAB
-XX:+TLABSize
-XX:TLABRefillWasteFraction
-XX:TLABWasteTargetPercent
-XX:+PrintTLAB <<<<<传送门
TLAB的数据结构:
class ThreadLocalAllocBuffer: public CHeapObj {
HeapWord* _start; // address of TLAB
HeapWord* _top; // address after last allocation
HeapWord* _pf_top; // allocation prefetch watermark
HeapWord* _end; // allocation end (excluding alignment_reserve)
size_t _desired_size; // desired size (including alignment_reserve)
size_t _refill_waste_limit; // hold onto tlab if free() is larger than this
.....................省略......................
}
- _start 指TLAB连续内存起始地址。
- _top 指TLAB当前分配到的地址。
- _end 指TLAB连续内存截止地址。
- _desired_size 是指TLAB的内存大小。
- _refill_waste_limit 是指最大的浪费空间。默认值为64b,jdk1.8<<<<<传送门
- eg:假设为_refill_waste_limit=5KB:
1、假如当前TLAB已经分配96KB,还剩下4KB可分配,但是现在new了一个对象需要6KB的空间,显然TLAB的内存不够了,4kb<5kb这时只浪费4KB的空间,在_refill_waste_limit 之内,这时可以申请一个新的TLAB空间,原先的TLAB交给Eden管理。
2、假如当前TLAB已经分配90KB,还剩下10KB,现在new了一个对象需要11KB,显然TLAB的内存不够了,这时就不能简单的抛弃当前TLAB,这11KB会被安排到Eden区进行申请。
- eg:假设为_refill_waste_limit=5KB:
分配规则:
1、obj_size + tlab_top <= tlab_end,直接在TLAB空间分配对象。
2、obj_size + tlab_top >= tlab_end && tlab_free > tlab_refill_waste_limit,对象不在TLAB分配,在Eden区分配。(tlab_free:剩余的内存空间,tlab_refill_waste_limit:允许浪费的内存空间)<----总结:tlab剩余可用空间>tlab可浪费空间,当前线程不能丢弃当前TLAB,本次申请交由Eden区分配空间。
3、 obj_size + tlab_top >= tlab_end && tlab_free < _refill_waste_limit,重新分配一块TLAB空间,在新的TLAB中分配对象。<----总结:tlab剩余可用空间
清单:/src/share/vm/memory/ThreadLocalAllocationBuffer.inline.hpp
功能:TLAB内存分配
inline HeapWord* ThreadLocalAllocBuffer::allocate(size_t size) {
invariants();
// 获取当前top
HeapWord* obj = top();
if (pointer_delta(end(), obj) >= size) {
// successful thread-local allocation
#ifdef ASSERT
// Skip mangling the space corresponding to the object header to
// ensure that the returned space is not considered parsable by
// any concurrent GC thread.
size_t hdr_size = oopDesc::header_size();
Copy::fill_to_words(obj + hdr_size, size - hdr_size, badHeapWordVal);
#endif // ASSERT
// This addition is safe because we know that top is
// at least size below end, so the add can't wrap.
// 重置top
set_top(obj + size);
invariants();
return obj;
}
return NULL;
}
实际上虚拟机内部会维护一个叫作refill_waste的值,当请求对象大于refill_waste时,会选择在堆中分配,若小于该值,则会废弃当前TLAB,新建TLAB来分配对象。这个阈值可以使用TLABRefillWasteFraction来调整,它表示TLAB中允许产生这种浪费的比例。默认值为64,即表示使用约为1/64的TLAB空间作为refill_waste。默认情况下,TLAB和refill_waste都会在运行时不断调整的,使系统的运行状态达到最优。如果想要禁用自动调整TLAB的大小,可以使用-XX:-ResizeTLAB禁用ResizeTLAB,并使用-XX:TLABSize手工指定一个TLAB的大小。
指针碰撞&Eden区分配
// 指针碰撞分配
HeapWord* compare_to = *Universe::heap()->top_addr();
HeapWord* new_top = compare_to + obj_size;
if (new_top <= *Universe::heap()->end_addr()) {
if (Atomic::cmpxchg_ptr(new_top, Universe::heap()->top_addr(), compare_to) != compare_to) {
goto retry;
}
result = (oop) compare_to;
}
}
Eden区指针碰撞,需要模拟多线程并发申请内存空间。且需要关闭逃逸分析 -XX:-DoEscapeAnalysis -XX:+UseTLAB
/**
* @author biudefu
* @since 2019/8/19 下午11:25
-Xmx100m -Xms100m -XX:-DoEscapeAnalysis -XX:+UseTLAB
-XX:TLABWasteTargetPercent=1 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails
*/
public class AllocationTLABSomeThread {
private static final int threadNum = 100;
private static CountDownLatch latch = new CountDownLatch(threadNum);
private static final int n = 50000000 / threadNum;
private static void alloc() {
byte[] b = new byte[100];
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < threadNum; i++) {
new Thread(() -> {
for (int j = 0; j < n; j++) {
alloc();
}
latch.countDown();
}).start();
}
try {
latch.await();
} catch (InterruptedException e) {
System.out.println("hello world");
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
运行结果:
-XX:-DoEscapeAnalysis -XX:InitialHeapSize=104857600 -XX:MaxHeapSize=104857600 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:TLABWasteTargetPercent=1 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC -XX:+UseTLAB
[GC (Allocation Failure) [PSYoungGen: 25600K->960K(29696K)] 25600K->968K(98304K), 0.0019559 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 26560K->960K(29696K)] 26568K->968K(98304K), 0.0022243 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 26560K->768K(29696K)] 26568K->776K(98304K), 0.0022446 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
........
[GC (Allocation Failure) [PSYoungGen: 32768K->0K(33280K)] 34193K->1425K(101888K), 0.0014598 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 32768K->0K(33280K)] 34193K->1425K(101888K), 0.0015168 secs] [Times: user=0.00 sys=0.01, real=0.00 secs]
823
Heap
PSYoungGen total 33280K, used 3655K [0x00000007bdf00000, 0x00000007c0000000, 0x00000007c0000000)
eden space 32768K, 11% used [0x00000007bdf00000,0x00000007be291c48,0x00000007bff00000)
from space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
to space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
ParOldGen total 68608K, used 1425K [0x00000007b9c00000, 0x00000007bdf00000, 0x00000007bdf00000)
object space 68608K, 2% used [0x00000007b9c00000,0x00000007b9d64798,0x00000007bdf00000)
Metaspace used 4255K, capacity 4718K, committed 4992K, reserved 1056768K
class space used 477K, capacity 533K, committed 640K, reserved 1048576K
关闭逃逸和TLAB分配 -XX:-DoEscapeAnalysis -XX:-UseTLAB
运行结果:
-XX:-DoEscapeAnalysis -XX:InitialHeapSize=104857600 -XX:MaxHeapSize=104857600 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:TLABWasteTargetPercent=1 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC -XX:-UseTLAB
[GC (Allocation Failure) [PSYoungGen: 25599K->976K(29696K)] 25599K->984K(98304K), 0.0023516 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 26575K->880K(29696K)] 26583K->888K(98304K), 0.0015459 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 26480K->832K(29696K)] 26488K->840K(98304K), 0.0006776 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
.......
[GC (Allocation Failure) [PSYoungGen: 32767K->0K(33280K)] 34053K->1285K(101888K), 0.0004838 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 32767K->0K(33280K)] 34053K->1285K(101888K), 0.0005389 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
5388
Heap
PSYoungGen total 33280K, used 21392K [0x00000007bdf00000, 0x00000007c0000000, 0x00000007c0000000)
eden space 32768K, 65% used [0x00000007bdf00000,0x00000007bf3e4230,0x00000007bff00000)
from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
ParOldGen total 68608K, used 1285K [0x00000007b9c00000, 0x00000007bdf00000, 0x00000007bdf00000)
object space 68608K, 1% used [0x00000007b9c00000,0x00000007b9d41788,0x00000007bdf00000)
Metaspace used 4248K, capacity 4718K, committed 4992K, reserved 1056768K
class space used 478K, capacity 533K, committed 640K, reserved 1048576K
经过对比,相差7倍左右。二者内存回收♻️,从YoungGC次数和耗时上没有太大变化:应为都是Eden区分配。