JMM and Hardware Intersection

589 단어·3 분·원문(.md)

The mapping between the Java Memory Model and actual hardware architectures like x86 and ARM is central to modern high-performance multi-threaded applications.

JMM and Hardware Abstraction Layers #

While the JMM provides developers with logical guarantees like Happens-Before, hardware employs out-of-order execution and cache hierarchies to maximize performance.

The JVM's role is to translate these logical instructions into assembly instructions that conform to each CPU's memory consistency model.

  • x86 (Intel/AMD) hardware uses TSO (Total Store Ordering), which maintains a fairly strict order for loads and stores. Reordering is limited to Store-Load reordering due to the Store Buffer, so the cost of inserting Java barriers is relatively low.
  • ARM (AArch64, Apple Silicon) is Weakly Ordered and very flexible, offering few ordering guarantees without explicit barriers. Load-Load, Load-Store, Store-Store, and most other reorderings are possible, and the performance difference when inserting barriers can be stark depending on the location.

Hardware Mapping by Keyword #

Volatile Variables (Load-Acquire / Store-Release) #

Hotspot JVM in Java 21 handles volatile variables with more than just simple memory reads.

  • x86 Architecture
    • Read: Uses a regular mov instruction, with no additional barriers needed thanks to TSO.
    • Write: Performs an empty lock operation like lock addl $0, 0(%rsp) after mov, or uses mfence. This prevents Store-Load reordering, ensuring visibility.
  • ARM Architecture (including LSE extension)
    • Read: Uses the ldar (Load-Acquire) instruction, which prevents subsequent operations from being reordered before this read.
    • Write: Uses the stlr (Store-Release) instruction, which prevents preceding operations from being reordered after this write. This is much more efficient than older dmb barriers.

CAS (Compare-And-Swap) and Atomic Operations #

CAS, which is central to VarHandle and Atomic classes, maps directly to hardware atomic instructions.

  • x86: Uses the lock cmpxchg instruction. The lock prefix acquires exclusive ownership of the entire cache line and simultaneously provides a memory barrier effect.
  • ARM: Historically used ldxr/stxr (Load-Link / Store-Conditional) loops, but in modern ARMv8.1+ environments where Java 21 runs, it directly uses the cas instruction to achieve atomicity at the hardware level.

OpenJDK Internal Memory Barrier Implementation #

Inside the JVM source code, in places like src/hotspot/os_cpu, the OrderAccess class is defined for each architecture.

  • LoadLoad (No-op): Utilizes dmb ishld or ldar
  • StoreStore (No-op): Utilizes dmb ishst or stlr
  • LoadStore (No-op): Utilizes dmb ish
  • StoreLoad (lock addl or mfence): dmb ish (the heaviest barrier)

On x86, the other three barriers (LoadLoad, StoreStore, LoadStore) are already guaranteed at the hardware level, so they are treated as No-ops (no operation), offering significant performance benefits. However, ARM must explicitly handle these.

Optimization Strategies #

Preventing False Sharing #

Still a critical issue in Java 21: if variables used by different threads are grouped within a CPU cache line (typically 64 bytes), a volatile write by one thread can invalidate the cache of another thread.

Using @jdk.internal.vm.annotation.Contended forces padding between fields (requires JVM option -XX:-RestrictContented).

Leveraging VarHandle (Opaque vs Plain) #

Not all shared variables need to be declared volatile. VarHandle, introduced in Java 9 and matured in 21, allows for more fine-grained control.

  • Opaque: Used when only value visibility is required, without ordering guarantees. On ARM, this can improve performance by using mov instructions, similar in cost to ldar/stlr.
  • Release/Acquire: Forms lighter barriers than full volatile, preventing reordering in specific directions.

Interaction with Virtual Threads #

Java 21 can create thousands of virtual threads. If I/O is performed within a synchronized block, the carrier thread can become pinned.

From a memory model perspective, using ReentrantLock is more advantageous in terms of virtual thread scheduling and hardware resource utilization.

JVM/jmm.md