Полный ассортимент каталогов:Глубокое погружение в JVM с JFR и журналами — обзор
Что такое ТЛАБ?
TLAB (Thread Local Allocation Buffer) локальный буфер выделения потока, который представляет собой область выделения памяти для конкретного потока. Поскольку это область выделения памяти, мы должны сначала выяснить, как выделяется память Java.
Обычно мы думаем, что новые объекты в Java размещаются в куче.Это утверждение недостаточно точное.Должно быть, что большинство объектов находится в куче.Назначение TLAB, и частьвыделение стекаилиПрямое выделение в куче, возможно, в области Eden или в старом поколении.. В то же время, для некоторых алгоритмов GC он также может быть выделен непосредственно на старом поколении, например, в G1 GC.огромные ассигнования, то есть когда объект превышает половину размера Региона, он сразу размещается в непрерывном пространстве старого поколения.
Здесь нас пока интересуют только распределения TLAB. Для однопоточных приложений при каждом выделении памяти будет записываться указатель в конце адреса памяти последнего выделенного объекта, а затем выделенный объект начнет извлекать выделение из этого указателя. Этот механизм называетсяbump-the-pointer(ударник). Для многопоточных приложений при распределении памяти необходимо учитывать безопасность потоков. Самая прямая идея — пройти глобальную блокировку, но это будет очень плохой производительностью. Чтобы оптимизировать эту производительность, мы считаем, что каждый поток может выделить локальный пул памяти потока, а затем использоватьbump-the-pointerмеханизм распределения памяти. Этот локальный частный пул памяти потока называется TLAB. Только когда TLAB заполнен, когда вы подаете заявку на память, вам нужно расширить TLAB или использовать новый TLAB, а затем вам нужна блокировка. Это значительно снижает использование блокировки.
Подробное объяснение параметров JVM, связанных с TLAB.
Давайте сначала рассмотрим параметры JVM, связанные с TLAB, и их значения.В следующем разделе мы углубимся в принцип анализа исходного кода и для чего предназначен этот параметр.
Следующие параметры и значения по умолчанию взяты из OpenJDK 11.
1. UseTLAB
инструкция: включать ли TLAB, он включен по умолчанию.
дефолт: истинный
Пример: если вы хотите закрыть:-XX:-UseTLAB
2. ResizeTLAB
инструкция: независимо от того, является ли TLAB адаптивной переменной, значение по умолчанию — «да».
дефолт: истинный
Пример: если вы хотите закрыть:-XX:-ResizeTLAB
3. TLABSize
инструкция: начальный размер TLAB. Единицы - байты
дефолт:0, 0 означает, что начальный размер TLAB активно не задается, а начальный размер каждого потока рассчитывается самой JVM
Пример:-XX:TLABSize=65536
4. MinTLABSize
инструкция: минимальный размер TLAB. Единицы - байты
дефолт: 2048
Пример:-XX:TLABSize=4096
5. TLABWasteTargetPercent
инструкция: расчет размера TLAB включает в себя размер области Эдема и соотношение, которое может быть потрачено впустую. Процент Эдема, занятого отходами TLAB, функция этого параметра будет подробно объяснена в следующем описании принципа.
дефолт:1
Пример:-XX:TLABWasteTargetPercent=10
6. TLABAllocationWeight
инструкция: Расчет размера TLAB связан с количеством потоков, но потоки создаются и уничтожаются динамически. Таким образом, необходимо рассчитать размер TLAB, определяя следующее количество потоков на основе количества исторических потоков. Как правило, такие функции прогнозирования в JVM используют для прогнозирования алгоритм EMA (экспоненциальное скользящее среднее), который будет подробно объяснен в следующем описании принципа. Этот параметр представляет собой вес. Чем выше вес, тем сильнее влияние последних данных.
дефолт: 35
Пример:-XX:TLABAllocationWeight=70
7. TLABRefillWasteFraction
инструкция: максимальный расход TLAB при повторном заполнении TLAB. Что же такое refill (заправка) и что такое TLAB отходы, то это будет подробно объяснено в следующем принципиальном описании.
дефолт: 64
Пример:-XX:TLABRefillWasteFraction=32
8. TLABWasteIncrement
инструкция: Допустимое увеличение потерь TLAB при медленном распределении TLAB, что такое потери TLAB и что такое медленное распределение TLAB, будут подробно объяснены в следующем описании принципа. Единицы - это не байты, аMarkWord
Число, которое является наименьшей единицей памяти в куче Java.
дефолт: 4
Пример:-XX:TLABWasteIncrement=4
9. ZeroTLAB
инструкция: обнулять ли все поля объектов во вновь созданных TLAB
дефолт: ложный
Пример:-XX:+ZeroTLAB
Подробное объяснение жизненного цикла и принципа действия TLAB
TLAB изКуча в районе ЭдемаВыделенный фрагмент локальной памяти потока.Когда поток инициализируется, если на JVM включен TLAB (он включен по умолчанию, к нему можно получить доступ через-XX:-UseTLAB
off), TLAB создается и инициализируется. В то же время вПервая попытка потока выделить объект после сканирования GC, который также создает и инициализирует TLAB.Когда TLAB заполнен или близок к заполнению, ТЛАБвозможныйбудет выпущен обратно в Эдем.Когда происходит сканирование GC объекта, TLAB возвращается в Эдем. Ожидается, что срок службы TLAB будет существовать только в течение цикла сканирования GC. В JVM,Цикл сканирования ГХ представляет собойepoch
. Затем вы можете знать, что память, выделенная в TLAB, должна распределяться линейно.
Минимальный размер TLAB:пройти черезMinTLABSize
уточнить
Максимальный размер TLAB: разные в разных GC,Огромный размер объекта в G1 GC, что составляет половину размера области G1. Как было сказано в начале, в G1 GC большие объекты не выделяются в TLAB, а в старом поколении.1/8 размера страницы в ZGC, аналогично в большинстве случаев SHenandoah GC также составляет 1/8 размера каждого региона.. Все они ожидают, что по крайней мере 7/8 области будут свободны от отступа, чтобы уменьшить сложность сканирования при выборе Cset. заДля других сборщиков мусора это максимальный размер массива int., что связано с заполнением пустой области TLAB, представленной фиктивным объектом.
Зачем заполнять фиктивный объект?
Так как TLAB знает только то, что выделено внутри потока, и возвращает область Eden, когда происходит сканирование GC, если она не заполнена, снаружи не известно, какая часть используется, а какая нет, нужно делать дополнительные проверки. Если заполнение было подтверждено, то оно будет истребовано.Объект, то есть фиктивный объект, GC пропустит эту память после маркировки напрямую, повысив эффективность сканирования. В любом случае, эта память уже принадлежит TLAB, и другие потоки не могут ее использовать до конца следующего сканирования. Этот фиктивный объект представляет собой массив int. Чтобы было место для заполнения фиктивного объекта,Как правило, размер TLAB резервирует место для заголовка фиктивного объекта., такжеint[]
заголовок, поэтому размер TLAB не может превышать максимальный размер массива int, иначе неиспользуемое пространство не может быть заполнено фиктивным объектом.
Размер TLAB: если указаноTLABSize
, используйте этот размер в качестве начального размера. Если не указано, рассчитывается по следующей формуле:Eden 区大小 / (当前 epcoh 内会分配对象期望线程个数 * 每个 epoch 内每个线程 refill 次数配置)
Ожидаемое количество потоков будет выделено в текущем epcoh., то есть количество потоков, которые будут создавать и инициализировать TLAB, которое предсказывается ранее упомянутым алгоритмом EMA (Exponential Moving Average). Алгоритм:
采样次数小于等于 100 时,每次采样:
1. 次数权重 = 100 / 次数
2. 计算权重 = 次数权重 与 TLABAllocationWeight 中大的那个
3. 新的平均值 = (100% - 计算权重%) * 之前的平均值 + 计算权重% * 当前采样值
采样次数大于 100 时,每次采样:
新的平均值 = (100% - TLABAllocationWeight %) * 之前的平均值 + TLABAllocationWeight % * 当前采样值
Как можно видетьTLABAllocationWeightЧем больше количество последних тем для этогоОжидаемое количество потоков будет выделено в следующем epcoh.тем больше влияние.
Ожидаемое количество пополнений в эпохуЭто количество заправок в каждом цикле сканирования ГХ. Так что же такое пополнение?
Выделение объектов, когда памяти TLAB достаточно,быстрое размещение, иначе объект выделяется, когда TLAB не хватает памятимедленное выделение,медленное выделениеВозможны два типа обработки:
1. Поток получает новый TLAB. Старый TLAB возвращается в Eden, после чего поток получает новый объект распределения TLAB. 2. Объекты размещаются за пределами TLAB, то есть в области Eden.
Эти два метода лечения в основномРешение TLAB о максимальном неиспользуемом пространстве, которое является динамическим значением..Исходное максимальное неиспользуемое пространство TLAB = Размер TLAB / TLABRefillWasteFraction. В соответствии с этим параметром JVM, упомянутым ранее, значение по умолчанию равноРазмер TLAB1/64 часть. После этого с каждыммедленное выделение,этоСамая большая трата места в TLABбудет увеличиваться каждый разTLABWasteIncrementразмер пространства. Если оставшаяся емкость текущего TLAB больше, чемСамая большая трата места в TLAB, он не будет размещен в текущем TLAB, а будет размещен непосредственно в области Eden. Если оставшаяся емкость меньшеСамая большая трата места в TLAB, он отбрасывает текущий TLAB и возвращается в Eden, а поток получает новый объект распределения TLAB.refillОтносится к поведению этого потока для получения нового объекта распределения TLAB.
Тогда легко понять, почему необходимо максимально удовлетворитьРазмер TLAB = Eden 区大小 / (下个 epcoh 内会分配对象期望线程个数 * 每个 epoch 内每个线程 refill 次数配置)
. Постарайтесь сделать так, чтобы все объекты размещались в TLAB, то есть TLAB может заполнить Эдем. Перед следующим сканированием GCrefillПамять, возвращенная в Eden, не может быть использована другими потоками, поскольку оставшееся пространство заполнено.dummy object. Таким образом, объем памяти, используемый всеми потоками, равен下个 epcoh 内会分配对象期望线程个数 * 每个 epoch 内每个线程 refill 次数配置
, объект обычно выделяется потоком в области Eden, то есть размер памяти, используемый всеми потоками, предпочтительно равен всему Eden. Но эта ситуация слишком идеальна, память всегда будет заполненаdummy objectЭто создает отходы, потому что сканирование GC может произойти в любое время. Предполагая, что в среднем половина памяти текущего TLAB каждого потока тратится впустую во время сканирования GC, это процентная доля потерянной памяти, используемой каждым потоком (т. е.TLABWasteTargetPercent), что равно (обратите внимание, что теряется только последний TLAB, прежде чемrefill возвращает предположение об отсутствии отходов):
1/2 * (每个 epoch 内每个线程期望 refill 次数) * 100
ТакКонфигурация количества заправок на поток в эпохуэквивалентно50 / TLABWasteTargetPercent
, по умолчанию 50 раз.
когдаTLABResizeПри значении true каждый разepochКогда потоку необходимо выделить объект,Размеры TLAB пересчитываются, и используйте этот последний размер для запроса памяти у Eden. Если нет выделения объекта, он не будет ни пересчитан, ни применен (ерунда~~~). В основном, чтобы разрешить поток TLABколичество заправокрядом сКонфигурация количества заправок на поток в эпоху. Это позволяет приблизить коэффициент отходов к заданному пользователем.TLABWasteTargetPercent, Формула для этого пересчета размера:TLAB 最新大小 * EMA refill 次数 / 每个 epoch 内每个线程 refill 次数配置
.
Подробное объяснение исходного кода, связанного с TLAB
1. Структура класса TLAB
Когда поток инициализируется, если в JVM включен TLAB (он включен по умолчанию и может быть отключен с помощью -XX:-UseTLAB), TLAB будет инициализирован.
TLAB включает в себя следующие поля (HeapWord* можно понимать как адрес памяти в куче):src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp
//静态全局变量
static size_t _max_size; // 所有 TLAB 的最大大小
static int _reserve_for_allocation_prefetch; // CPU 缓存优化 Allocation Prefetch 的保留空间,这里先不用关心
static unsigned _target_refills; //每个 epoch 周期内期望的 refill 次数
//以下是 TLAB 的主要构成 field
HeapWord* _start; // TLAB 起始地址,表示堆内存地址都用 HeapWord*
HeapWord* _top; // 上次分配的内存地址
HeapWord* _end; // TLAB 结束地址
size_t _desired_size; // TLAB 大小 包括保留空间,表示内存大小都需要通过 size_t 类型,也就是实际字节数除以 HeapWordSize 的值
size_t _refill_waste_limit; // TLAB最大浪费空间,剩余空间不足分配浪费空间限制。在TLAB剩余空间不足的时候,根据这个值决定分配策略,如果浪费空间大于这个值则直接在 Eden 区分配,如果小于这个值则将当前 TLAB 放回 Eden 区管理并从 Eden 申请新的 TLAB 进行分配。
AdaptiveWeightedAverage _allocation_fraction; // 当前 TLAB 占用所有TLAB最大空间(一般是Eden大小)的期望比例,通过 EMA 算法采集预测
//以下是我们这里不用太关心的 field
HeapWord* _allocation_end; // TLAB 真正可以用来分配内存的结束地址,这个是 _end 结束地址排除保留空间,至于为何需要保留空间我们这里先不用关心,稍后我们会解释这个参数
HeapWord* _pf_top; // Allocation Prefetch CPU 缓存优化机制相关需要的参数,这里先不用考虑
size_t _allocated_before_last_gc; // GC统计数据采集相关,例如线程内存申请数据统计等等,这里先不用关心
unsigned _number_of_refills; // 线程分配内存数据采集相关,TLAB 剩余空间不足分配次数
unsigned _fast_refill_waste; // 线程分配内存数据采集相关,TLAB 快速分配浪费,什么是快速分配,待会会说到
unsigned _slow_refill_waste; // 线程分配内存数据采集相关,TLAB 慢速分配浪费,什么是慢速分配,待会会说到
unsigned _gc_waste; // 线程分配内存数据采集相关,gc浪费
unsigned _slow_allocations; // 线程分配内存数据采集相关,TLAB 慢速分配计数
size_t _allocated_size; //分配的内存大小
size_t _bytes_since_last_sample_point; // JVM TI 采集指标相关 field,这里不用关心
2. Инициализация TLAB
Во-первых, при запуске 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.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 инициализирует различные поля TLAB, упомянутые выше, о которых нам нужно позаботиться: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();
}
2.1 Как рассчитывается начальный ожидаемый размер?
src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp
//计算初始大小
size_t ThreadLocalAllocBuffer::initial_desired_size() {
size_t init_sz = 0;
//如果通过 -XX:TLABSize 设置了 TLAB 大小,则用这个值作为初始期望大小
//表示堆内存占用大小都需要用占用几个 HeapWord 表示,所以用TLABSize / HeapWordSize
if (TLABSize > 0) {
init_sz = TLABSize / HeapWordSize;
} else {
//获取当前epoch内线程数量期望,这个如之前所述通过 EMA 预测
unsigned int nof_threads = ThreadLocalAllocStats::allocating_threads_avg();
//不同的 GC 实现有不同的 TLAB 容量,Universe::heap()->tlab_capacity(thread()) 一般是 Eden 区大小
//例如 G1 GC,就是等于 (_policy->young_list_target_length() - _survivor.length()) * HeapRegion::GrainBytes,可以理解为年轻代减去Survivor区,也就是Eden区
//整体大小等于 Eden区大小/(当前 epcoh 内会分配对象期望线程个数 * 每个 epoch 内每个线程 refill 次数配置)
//target_refills已经在 JVM 初始化所有 TLAB 全局配置的时候初始化好了
init_sz = (Universe::heap()->tlab_capacity(thread()) / HeapWordSize) /
(nof_threads * target_refills());
//考虑对象对齐,得出最后的大小
init_sz = align_object_size(init_sz);
}
//保持大小在 min_size() 还有 max_size() 之间
//min_size主要由 MinTLABSize 决定
init_sz = MIN2(MAX2(init_sz, min_size()), max_size());
return init_sz;
}
//最小大小由 MinTLABSize 决定,需要表示为 HeapWordSize,并且考虑对象对齐,最后的 alignment_reserve 是 dummy object 填充的对象头大小(这里先不考虑 JVM 的 CPU 缓存 prematch,我们会在其他章节详细分析)。
static size_t min_size() {
return align_object_size(MinTLABSize / HeapWordSize) + alignment_reserve();
}
2.2 Как определяется максимальный размер TLAB?
Различные методы GC имеют разные методы:
Огромный размер объекта в G1 GC, что составляет половину размера области G1:src/hotspot/share/gc/g1/g1CollectedHeap.cpp
// For G1 TLABs should not contain humongous objects, so the maximum TLAB size
// must be equal to the humongous object limit.
size_t G1CollectedHeap::max_tlab_size() const {
return align_down(_humongous_object_threshold_in_words, MinObjAlignment);
}
1/8 размера страницы в ZGC, аналогично в большинстве случаев SHenandoah GC также составляет 1/8 размера каждого региона.. Все они ожидают, что по крайней мере 7/8 области будут свободны от отступа, чтобы уменьшить сложность сканирования при выборе Cset:src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
MaxTLABSizeWords = MIN2(ShenandoahElasticTLAB ? RegionSizeWords : (RegionSizeWords / 8), HumongousThresholdWords);
src/hotspot/share/gc/z/zHeap.cpp
const size_t ZObjectSizeLimitSmall = ZPageSizeSmall / 8;
заДля других сборщиков мусора это максимальный размер массива int., что связано с заполнением пустой области TLAB, представленной фиктивным объектом. Причина этого была объяснена ранее.
3. TLAB выделяет память
Когда вы создаете новый объект, вам нужно позвонитьinstanceOop InstanceKlass::allocate_instance(TRAPS)
src/hotspot/share/oops/instanceKlass.cpp
instanceOop InstanceKlass::allocate_instance(TRAPS) {
bool has_finalizer_flag = has_finalizer(); // Query before possible GC
int size = size_helper(); // Query before forming handle.
instanceOop i;
i = (instanceOop)Universe::heap()->obj_allocate(this, size, CHECK_NULL);
if (has_finalizer_flag && !RegisterFinalizersAtInit) {
i = register_finalizer(i, CHECK_NULL);
}
return i;
}
Его ядроheap()->obj_allocate(this, size, CHECK_NULL)
Выделить память из кучи:src/hotspot/share/gc/shared/collectedHeap.inline.hpp
inline oop CollectedHeap::obj_allocate(Klass* klass, int size, TRAPS) {
ObjAllocator allocator(klass, size, THREAD);
return allocator.allocate();
}
использовать глобальныйObjAllocator
Реализовать выделение памяти объекта:src/hotspot/share/gc/shared/memAllocator.cpp
oop MemAllocator::allocate() const {
oop obj = NULL;
{
Allocation allocation(*this, &obj);
//分配堆内存,继续看下面一个方法
HeapWord* mem = mem_allocate(allocation);
if (mem != NULL) {
obj = initialize(mem);
} else {
// The unhandled oop detector will poison local variable obj,
// so reset it to NULL if mem is NULL.
obj = NULL;
}
}
return obj;
}
HeapWord* MemAllocator::mem_allocate(Allocation& allocation) const {
//如果使用了 TLAB,则从 TLAB 分配,分配代码继续看下面一个方法
if (UseTLAB) {
HeapWord* result = allocate_inside_tlab(allocation);
if (result != NULL) {
return result;
}
}
//否则直接从 tlab 外分配
return allocate_outside_tlab(allocation);
}
HeapWord* MemAllocator::allocate_inside_tlab(Allocation& allocation) const {
assert(UseTLAB, "should use UseTLAB");
//从当前线程的 TLAB 分配内存,TLAB 快分配
HeapWord* mem = _thread->tlab().allocate(_word_size);
//如果没有分配失败则返回
if (mem != NULL) {
return mem;
}
//如果分配失败则走 TLAB 慢分配,需要 refill 或者直接从 Eden 分配
return allocate_inside_tlab_slow(allocation);
}
3.1 Быстрое выделение TLAB
src/hotspot/share/gc/shared/threadLocalAllocBuffer.inline.hpp
inline HeapWord* ThreadLocalAllocBuffer::allocate(size_t size) {
//验证各个内存指针有效,也就是 _top 在 _start 和 _end 范围内
invariants();
HeapWord* obj = top();
//如果空间足够,则分配内存
if (pointer_delta(end(), obj) >= size) {
set_top(obj + size);
invariants();
return obj;
}
return NULL;
}
3.2 Медленное выделение TLAB
src/hotspot/share/gc/shared/memAllocator.cpp
HeapWord* MemAllocator::allocate_inside_tlab_slow(Allocation& allocation) const {
HeapWord* mem = NULL;
ThreadLocalAllocBuffer& tlab = _thread->tlab();
// 如果 TLAB 剩余空间大于 最大浪费空间,则记录并让最大浪费空间递增
if (tlab.free() > tlab.refill_waste_limit()) {
tlab.record_slow_allocation(_word_size);
return NULL;
}
//重新计算 TLAB 大小
size_t new_tlab_size = tlab.compute_size(_word_size);
//TLAB 放回 Eden 区
tlab.retire_before_allocation();
if (new_tlab_size == 0) {
return NULL;
}
// 计算最小大小
size_t min_tlab_size = ThreadLocalAllocBuffer::compute_min_size(_word_size);
//分配新的 TLAB 空间,并在里面分配对象
mem = Universe::heap()->allocate_new_tlab(min_tlab_size, new_tlab_size, &allocation._allocated_tlab_size);
if (mem == NULL) {
assert(allocation._allocated_tlab_size == 0,
"Allocation failed, but actual size was updated. min: " SIZE_FORMAT
", desired: " SIZE_FORMAT ", actual: " SIZE_FORMAT,
min_tlab_size, new_tlab_size, allocation._allocated_tlab_size);
return NULL;
}
assert(allocation._allocated_tlab_size != 0, "Allocation succeeded but actual size not updated. mem at: "
PTR_FORMAT " min: " SIZE_FORMAT ", desired: " SIZE_FORMAT,
p2i(mem), min_tlab_size, new_tlab_size);
//如果启用了 ZeroTLAB 这个 JVM 参数,则将对象所有字段置零值
if (ZeroTLAB) {
// ..and clear it.
Copy::zero_to_words(mem, allocation._allocated_tlab_size);
} else {
// ...and zap just allocated object.
}
//设置新的 TLAB 空间为当前线程的 TLAB
tlab.fill(mem, mem + _word_size, allocation._allocated_tlab_size);
//返回分配的对象内存地址
return mem;
}
3.2.1 Самая большая трата места в TLAB
Самая большая трата места в TLAB_refill_waste_limit
Начальное значение — это размер TLAB, разделенный на TLABRefillWasteFraction:src/hotspot/share/gc/shared/threadLocalAllocBuffer.hpp
size_t initial_refill_waste_limit() { return desired_size() / TLABRefillWasteFraction; }
Каждое медленное выделение, вызовrecord_slow_allocation(size_t obj_size)
При записи медленных выделений увеличьте размер самого большого неиспользуемого пространства TLAB:
src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp
void ThreadLocalAllocBuffer::record_slow_allocation(size_t obj_size) {
//每次慢分配,_refill_waste_limit 增加 refill_waste_limit_increment,也就是 TLABWasteIncrement
set_refill_waste_limit(refill_waste_limit() + refill_waste_limit_increment());
_slow_allocations++;
log_develop_trace(gc, tlab)("TLAB: %s thread: " INTPTR_FORMAT " [id: %2d]"
" obj: " SIZE_FORMAT
" free: " SIZE_FORMAT
" waste: " SIZE_FORMAT,
"slow", p2i(thread()), thread()->osthread()->thread_id(),
obj_size, free(), refill_waste_limit());
}
//refill_waste_limit_increment 就是 JVM 参数 TLABWasteIncrement
static size_t refill_waste_limit_increment() { return TLABWasteIncrement; }
3.2.2 Пересчет размера TLAB
src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp
_desired_size
Когда это стало? Как это стало?
void ThreadLocalAllocBuffer::resize() {
assert(ResizeTLAB, "Should not call this otherwise");
//根据 _allocation_fraction 这个 EMA 采集得出平均数乘以Eden区大小,得出 TLAB 当前预测占用内存比例
size_t alloc = (size_t)(_allocation_fraction.average() *
(Universe::heap()->tlab_capacity(thread()) / HeapWordSize));
//除以目标 refill 次数就是新的 TLAB 大小,和初始化时候的结算方法差不多
size_t new_size = alloc / _target_refills;
//保证在 min_size 还有 max_size 之间
new_size = clamp(new_size, min_size(), max_size());
size_t aligned_new_size = align_object_size(new_size);
log_trace(gc, tlab)("TLAB new size: thread: " INTPTR_FORMAT " [id: %2d]"
" refills %d alloc: %8.6f desired_size: " SIZE_FORMAT " -> " SIZE_FORMAT,
p2i(thread()), thread()->osthread()->thread_id(),
_target_refills, _allocation_fraction.average(), desired_size(), aligned_new_size);
//设置新的 TLAB 大小
set_desired_size(aligned_new_size);
//重置 TLAB 最大浪费空间
set_refill_waste_limit(initial_refill_waste_limit());
}
когда это называетсяresize
как насчет? Обычно каждый раз, когда GC завершается. Большая часть GC находится вgc_epilogue
метод вызова TLAB каждого потока дляresize
Терять.
4. Переработка ТЛАБ
Переработка TLAB означает, что поток отбрасывает текущий TLAB обратно в область Eden. Существует два момента времени для перезапуска TLAB: во-первых, при выделении объекта оставшегося места в TLAB недостаточно.Когда TLAB заполнен, но неиспользуемое пространство меньше максимального неиспользуемого пространства, текущий TLAB перезапускается и создается новый. получается.Во-вторых, когда происходит GC, точнее сказать, когда GC начинает сканировать.. Разные сборщики мусора могут быть реализованы по-разному, но время в основном одинаковое.Вот пример сборщика мусора G1:
src/hotspot/share/gc/g1/g1CollectedHeap.cpp
void G1CollectedHeap::gc_prologue(bool full) {
//省略其他代码
// Fill TLAB's and such
{
Ticks start = Ticks::now();
//确保堆内存是可以解析的
ensure_parsability(true);
Tickspan dt = Ticks::now() - start;
phase_times()->record_prepare_tlab_time_ms(dt.seconds() * MILLIUNITS);
}
//省略其他代码
}
Зачем следить за тем, чтобы память кучи анализировалась? Это способствует более быстрому сканированию объектов в куче. Убедитесь, что память может быть разрешена, что происходит внутри?
void CollectedHeap::ensure_parsability(bool retire_tlabs) {
//真正的 GC 肯定发生在安全点上,这个在后面安全点章节会详细说明
assert(SafepointSynchronize::is_at_safepoint() || !is_init_completed(),
"Should only be called at a safepoint or at start-up");
ThreadLocalAllocStats stats;
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *thread = jtiwh.next();) {
BarrierSet::barrier_set()->make_parsable(thread);
//如果全局启用了 TLAB
if (UseTLAB) {
//如果指定要回收,则回收 TLAB
if (retire_tlabs) {
//回收 TLAB 其实就是将 ThreadLocalAllocBuffer 的堆内存指针 MarkWord 置为 NULL
thread->tlab().retire(&stats);
} else {
//当前如果不回收,则将 TLAB 填充 Dummy Object 利于解析
thread->tlab().make_parsable();
}
}
}
stats.publish();
}
Краткое изложение основных процессов TLAB
JFR мониторинг TLAB
В соответствии с приведенными выше принципами и анализом исходного кода становится известно, что TLAB является частью области Eden и в основном используется для локального размещения объектов потока. Когда объектная память выделяется при заполнении TLAB, могут происходить два процесса:
- Поток получает новый TLAB. Старый TLAB возвращается в Eden, Eden управляет им, а затем поток распределяет объекты через новый TLAB.
- Объекты размещаются за пределами TLAB, также известной как область Eden.
Для процесса получения потоком нового TLAB, то есть пополнения, в соответствии с принципом построения TLAB, это происходит часто и может происходить несколько раз в каждую эпоху. Но объекты располагаются непосредственно в районе Эдема, чего мы хотим избежать. JFR для
JFR имеет разные события для мониторинга этих двух процессов. соответственноjdk.ObjectAllocationOutsideTLAB
иjdk.ObjectAllocationInNewTLAB
.jdk.ObjectAllocationInNewTLAB
Что касается пополнения, нам, как правило, не нужно отслеживать (при условии, что вы не изменяете параметры TLAB по умолчанию).Значение использования этого теста и изучения TLAB больше, чем значение мониторинга.jdk.ObjectAllocationOutsideTLAB
Соответствующий объект находится непосредственно в районе Эдема, за которым нам нужно следить. Что касается того, как не повлиять на мониторинг безопасности онлайн-производительности, как просматривать и анализировать, как решать и генерировать тесты, эти два события будут подробно проанализированы в следующем разделе.
в то же время