HotSpot JVM ZGC Barriers, NUMA Memory Allocation, and Safepoint Operations

1,321 단어·3 분·원문(.md)

Let's dissect the low-level operations of ZGC Generational introduction, NUMA architecture advancements, and Safepoint mechanisms, based on the latest Java 21 LTS, examining them at the OS, hardware, and C++ runtime source code levels.

ZGC's Colored Pointers, Load/Store Barriers #

With the introduction of Generational ZGC (JEP 439) in Java 21, ZGC underwent significant changes. Previously, only load barriers were active, but now, Store Barriers play a crucial role in distinguishing between young and old generations and tracking inter-generational references (Remembered Set).

Color Pointers #

ZGC uses a portion (e.g., 44-48 bits) of the 64-bit pointer space to point to the actual memory address of an object, using the remaining upper bits as metadata (color).

  • Bit Composition: Represents states such as Marked0, Marked1, Remapped, Finalizable.
  • Hardware/OS Mapping: In the past, ZGC used multi-mapping (Virtual Memory Aliasing) to map multiple virtual addresses to the same physical page. However, in environments supporting Address Marking on modern x64 architectures or TBI-TopByteIgnore on AArch64, it leverages CPU hardware features to ignore metadata bits and dereference to the actual address, without OS-level tricks.

Load/Store Barrier #

A barrier is a snippet of code forcibly inserted by the JIT Compiler (C2) when generating machine code to read or write object references.

Calling the C++ runtime for every memory access, known as the Fast Path (Assembly level inline code), could collapse performance. Therefore, the JIT compiler directly inlines the Fast Path in assembly via HotSpot's ZBarrierSetC2 class.

  • Load Barrier: When loading a pointer, it compares the current ZGC Good Color mask with the pointer's color bits using a bitwise AND or CMP operation.
mov r10, [rax + offset]      ; 메모리에서 포인터 로드
test r10, [Good_Color_Mask]  ; 현재 올바른 상태(Remapped 등)인지 비트 검사
jnz slow_path_stub           ; 색상이 맞지 않으면(Bad Color) Slow path로 점프
; If colors match, proceed (Fast Path)

Slow Path #

If jnz leads to the slow path, the assembly stub calls a C++ runtime function within HotSpot.

  • Functions within the src/hotspot/share/gc/z/zBarrierSetRuntime.cpp file (e.g., ZBarrierSetRuntime::load_barrier_on_oop_field_preloaded) are called.
  • Load Barrier Slow Path: If an object is undergoing relocation, it consults the forwarding table to find the new address (heal), updates the pointer's color to Remapped, and then returns the latest address. This process uses CAS operations to safely handle multi-threaded contention.
  • Store Barrier (Generational ZGC): Occurs when writing a new reference to an object's field. It checks if an old object is pointing to a young object, and if so, records the memory region address of that old object in ZGC's Remembered Set via ZRememberedSet::record_store so it can be used as a root during the next young GC.

TLAB & PLAB Allocation Flow and OS Page / NUMA Architecture Integration #

Object allocation is the alpha and omega of JVM performance. Both ZGC and G1 fundamentally use a bump-the-pointer allocation strategy.

Bringing this into a thread-local context are TLAB (Thread Local Allocation Buffer) and PLAB (Promotion Local Allocation Buffer).

  • TLAB (Application Thread): Used by Java applications when creating new objects. Managed by the ThreadLocalAllocbuffer C++ class, it performs bump allocation by simply incrementing a pointer within its own buffer, without locks.
  • PLAB (GC Worker Thread): A local buffer used by GC threads when copying surviving objects to other regions (young -> old) or during evacuation (promotion/relocation). This also aims to eliminate lock contention among GC threads.

OS memory pages and NUMA go beyond mere memory allocation; their purpose is to explore mapping with physical NUMA hardware.

If the +UseNUMA option is enabled, HotSpot behaves as follows:

  1. Virtual Memory Reservation and ZPage Allocation: When a TLAB fills up and a new TLAB is requested, the JVM provides a chunk of a certain size (a ZPage in ZGC's case) from the heap's young generation to the thread. At this point, the HotSpot memory manager desires the ZPage to be allocated on the NUMA node of the CPU where the current thread is executing.
  2. OS First-Touch Policy: Modern OSes like Linux, even when memory is allocated via mmap, initially keep it in a virtual state not mapped to actual physical RAM (Page Frame). When a write operation first occurs at that memory address (page fault), the OS allocates physical memory from the local NUMA node of the CPU core to which the writing thread belongs. This is known as the first-touch policy.
  3. HotSpot's Intervention (os:numa_make_local): At the C++ level, when HotSpot allocates a new TLAB region, it performs a zeroing operation to fill that region with zeros. While this serves security purposes, it is also a sophisticated technique to force the OS to allocate the TLAB's physical pages to the current CPU's NUMA node by making the executing thread directly touch the memory.
  4. PLAB's NUMA Locality: In Generational ZGC, when GC threads promote objects to the old generation, they use PLABs. Even then, ZGC's NUMA-aware allocator preferentially allocates ZPages for PLABs from NUMA nodes close to the original object or the executing GC thread, minimizing memory access latency.

Safepoint's Polling Page Mechanism (OS Page Fault Based) #

A safepoint is a mechanism that stops all Java threads in a safe state for purposes such as GC, deoptimization, or thread dumps.

The answer to how threads are stopped without an interrupt signal lies in the combination of the polling page and the OS's memory protection page fault mechanism.

Polling Page Initialization and JIT Compilation #

  1. Polling Page Allocation: During JVM bootstrap, C++ initialization code like os::init_2 allocates a specific memory page, the safepoint polling page, via mmap. Normally, this page is readable.
  2. JIT Compiler Polling Code Insertion: C1 and C2 compilers insert polling instructions at strategic locations, such as method return points or loop backedges.
    1. test eax, [rip + polling_page_address] (an action that compares a specific register value with the value at the polling page address or simply reads it)
    2. Normally, since this page is readable, this assembly instruction passes with almost zero overhead on a cache hit, faster than a software if check.

Safepoint Reaching Mechanism #

When the VM Thread (JVM's internal global control thread) needs to trigger a safepoint for GC or other operations, the following occurs:

  1. The VM Thread calls the os::make_polling_page_unreachable() C++ function. This function internally executes the Linux system call mprotect(polling_page_addr, page_size, PROT_NONE). At this moment, the polling page transitions to a non-readable/non-writable state (PROT_NONE).
  2. Java Thread Page Fault (SIGSEGV) Occurs: Application threads, while executing code, encounter the test instruction inserted by the JIT (as in the example above) and attempt to read the polling page. However, since the VM thread just changed this page's permissions to PROT_NONE, a Page Fault occurs in the hardware MMU, and the OS sends a SIGSEGV signal to that thread.
  3. HotSpot Signal Handler Intervention (os_linux_x86.cpp - JVM_handle_linux_signal): While a typical C++ program would crash upon receiving a SIGSEGV, HotSpot has registered its own custom signal handler.
    1. Handler Logic: "Check if the memory address info->si_addr of the recently occurred SIGSEGV matches our configured polling page."
    2. If it matches? -> It recognizes this not as an error, but as a safepoint request.
  4. Thread Blocking and Safepoint Synchronization: The signal handler does not crash the program code. Instead, it changes the current thread's state from _thread_in_Java to _thread_blocked, attempts to acquire an internal C++ monitor/mutex, and puts itself into a park (waiting) state. Once all Java threads fall into this trap and are blocked, the VM thread finally recognizes that the safepoint has been reached and begins GC or other operations.

In this way, HotSpot implements an elegant hack that leverages the hardware Memory Management Unit (MMU) and the OS's page fault mechanism to the extreme at runtime (zero-cost overhead during normal operation) to avoid the overhead of software branch checks like if (need_safepoint).

JVM/memory_concept_hard.md