全网最硬核 JVM TLAB 分析(单篇版不包含额外加菜) (7)

首先是 JVM 启动的时候,全局 TLAB 需要初始化:
src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp

void ThreadLocalAllocBuffer::startup_initialization() { //初始化,也就是归零统计数据 ThreadLocalAllocStats::initialize(); // 假设平均下来,GC 扫描的时候,每个线程当前的 TLAB 都有一半的内存被浪费,这个每个线程使用内存的浪费的百分比率(也就是 TLABWasteTargetPercent),也就是等于(注意,仅最新的那个 TLAB 有浪费,之前 refill 退回的假设是没有浪费的):1/2 * (每个 epoch 内每个线程期望 refill 次数) * 100 //那么每个 epoch 内每个线程 refill 次数配置就等于 50 / TLABWasteTargetPercent, 默认也就是 50 次。 _target_refills = 100 / (2 * TLABWasteTargetPercent); // 但是初始的 _target_refills 需要设置最多不超过 2 次来减少 VM 初始化时候 GC 的可能性 _target_refills = MAX2(_target_refills, 2U); //如果 C2 JIT 编译存在并启用,则保留 CPU 缓存优化 Allocation Prefetch 空间,这个这里先不用关心,会在别的章节讲述 #ifdef COMPILER2 if (is_server_compilation_mode_vm()) { int lines = MAX2(AllocatePrefetchLines, AllocateInstancePrefetchLines) + 2; _reserve_for_allocation_prefetch = (AllocatePrefetchDistance + AllocatePrefetchStepSize * lines) / (int)HeapWordSize; } #endif // 初始化 main 线程的 TLAB guarantee(Thread::current()->is_Java_thread(), "tlab initialization thread not Java thread"); Thread::current()->tlab().initialize(); log_develop_trace(gc, tlab)("TLAB min: " SIZE_FORMAT " initial: " SIZE_FORMAT " max: " SIZE_FORMAT, min_size(), Thread::current()->tlab().initial_desired_size(), max_size()); }

每个线程维护自己的 TLAB,同时每个线程的 TLAB 大小不一。TLAB 的大小主要由 Eden 的大小,线程数量,还有线程的对象分配速率决定。
在 Java 线程开始运行时,会先分配 TLAB:
src/hotspot/share/runtime/thread.cpp

void JavaThread::run() { // initialize thread-local alloc buffer related fields this->initialize_tlab(); //剩余代码忽略 }

分配 TLAB 其实就是调用 ThreadLocalAllocBuffer 的 initialize 方法。
src/hotspot/share/runtime/thread.hpp

void initialize_tlab() { //如果没有通过 -XX:-UseTLAB 禁用 TLAB,则初始化TLAB if (UseTLAB) { tlab().initialize(); } } // Thread-Local Allocation Buffer (TLAB) support ThreadLocalAllocBuffer& tlab() { return _tlab; } ThreadLocalAllocBuffer _tlab;

ThreadLocalAllocBuffer 的 initialize 方法初始化 TLAB 的上面提到的我们要关心的各种 field:
src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp

void ThreadLocalAllocBuffer::initialize() { //设置初始指针,由于还没有从 Eden 分配内存,所以这里都设置为 NULL initialize(NULL, // start NULL, // top NULL); // end //计算初始期望大小,并设置 set_desired_size(initial_desired_size()); //所有 TLAB 总大小,不同的 GC 实现有不同的 TLAB 容量, 一般是 Eden 区大小 //例如 G1 GC,就是等于 (_policy->young_list_target_length() - _survivor.length()) * HeapRegion::GrainBytes,可以理解为年轻代减去Survivor区,也就是Eden区 size_t capacity = Universe::heap()->tlab_capacity(thread()) / HeapWordSize; //计算这个线程的 TLAB 期望占用所有 TLAB 总体大小比例 //TLAB 期望占用大小也就是这个 TLAB 大小乘以期望 refill 的次数 float alloc_frac = desired_size() * target_refills() / (float) capacity; //记录下来,用于计算 EMA _allocation_fraction.sample(alloc_frac); //计算初始 refill 最大浪费空间,并设置 //如前面原理部分所述,初始大小就是 TLAB 的大小(_desired_size) / TLABRefillWasteFraction set_refill_waste_limit(initial_refill_waste_limit()); //重置统计 reset_statistics(); } 9.2.1. 初始期望大小是如何计算的呢?

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppzys.html