JMM and Hardware Intersection
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
movinstruction, with no additional barriers needed thanks to TSO. - Write: Performs an empty
lockoperation likelock addl $0, 0(%rsp)aftermov, or usesmfence. This prevents Store-Load reordering, ensuring visibility.
- Read: Uses a regular
- 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 olderdmbbarriers.
- Read: Uses the
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 cmpxchginstruction. Thelockprefix 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 thecasinstruction 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 ishldorldar - StoreStore (No-op): Utilizes
dmb ishstorstlr - LoadStore (No-op): Utilizes
dmb ish - StoreLoad (
lock addlormfence):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
movinstructions, similar in cost toldar/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.