Implementing the Java Memory Model

The Java Memory Model (JMM) (JSR-133, Java Language Specification Third Edition, Chapter 17) defines memory ordering and visibility properties in terms of the happens-before ordering. This imposes a range of constraints concerning load/store of regular variables, load/store of volatile variables, monitor entry and monitor exit.

The JSR-133 Cookbook explains the requirements in terms of various forms of memory-barrier, and gives examples of how the different kinds of barriers can be implemented on different architectures. For reference we reproduce the following table:

Required Barriers 2nd operation

1st operation Normal Load Normal Store Volatile Load Volatile Store MonitorEnter MonitorExit
Normal Load


LoadStore
LoadStore
Normal Store



StoreStore

StoreExit
Volatile Load LoadLoad

LoadStore LoadLoad LoadStore LoadEnter LoadExit
Volatile Store

StoreLoad StoreStore StoreEnter StoreExit
MonitorEnter EnterLoad EnterStore EnterLoad EnterStore

EnterEnter EnterExit
MonitorExit

ExitLoad ExitStore ExitEnter ExitExit

In this table, "Enter" is the same as "Load" and "Exit" is the same as "Store", unless overridden by the use and nature of atomic instructions. In particular:

While the JMM expresses the necessary barriers between pairs of instructions, Hotspot does not operate this way as it only considers each individual instruction. Consequently, Hotspot has to introduce the worst-case barrier needed into the prolog or epilog of an instruction execution. This can lead to excessive use of barriers in some cases, though there are potentially opportunities to elide redundant barriers in some cases.

JMM Volatile Barriers in the C1 Client Compiler

The barriers are emitted at the LIR level in c1_LIRGenerator.cpp:

void LIRGenerator::do_StoreField(StoreField* x) {
  ...
  if (is_volatile && os::is_MP()) {
    __ membar_release();
  }
  ...
  if (is_volatile) {
    volatile_field_store(value.result(), address, info);

  ... 
  if (is_volatile && os::is_MP()) {
    __ membar();
  }
}

void LIRGenerator::do_LoadField(LoadField* x) {
  ...
  if (is_volatile) {
    volatile_field_load(address, reg, info);

  ...
  if (is_volatile && os::is_MP()) {
    __ membar_acquire();
  }
}

First note that the barriers here are expressed in the acquire/release/membar form not the loadload/loadstore etc form used by the JSR-133 Cookbook. This impedance mismatch is unfortunate and we should really fix it. Each architecture is responsible for defining what needs to be done for each of the membar forms.

Volatile Accesses

Given the above placement of barriers by C1 let us examine what instruction sequences we actually get for the different instruction pairings described in the JMM table above. Monitor operations are elided for now as we have not examined what barriers are involved in their use.

First InstructionSecond InstructionGenerated SequenceRequired BarrierActual Barrier
Normal load Volatile load load ; volatile_load; membar_acquire; Nil Nil
Normal store Volatile load store ; volatile_load; membar_acquire; Nil Nil
Volatile load Volatile load volatile_load; membar_acquire; volatile_load; membar_acquire; LoadLoad membar_acquire
Volatile store Volatile load membar_release; volatile_store; membar; volatile_load; membar_acquire; StoreLoad membar
Volatile load Normal load volatile_load; membar_acquire; load; LoadLoad membar_acquire
Volatile load Normal store volatile_load; membar_acquire; store; LoadStore membar_acquire
Volatile load Volatile store volatile_load; membar_acquire; membar_release; volatile_store; membar; LoadStore membar_acquire + membar_release
Normal load Volatile store load ; membar_release; volatile_store; membar; LoadStore membar_release
Normal store Volatile store store ; membar_release; volatile_store; membar; StoreStore membar_release
Volatile load Volatile store volatile_load; membar_acquire; membar_release; volatile_store; membar; LoadStore membar_acquire + membar_release
Volatile store Volatile store membar_release; volatile_store; membar; membar_release; volatile_store; membar; StoreStore membar + membar_release
Volatile store Normal load membar_release; volatile_store; membar; load; Nil membar
Volatile store Normal store membar_release; volatile_store; membar; store; Nil membar

Using this table we can see that:

membar_acquire := LoadLoad + LoadStore
membar_release := LoadStore + StoreStore
membar         := StoreLoad
Note these are not describing summative operations but merely saying that the barrier on the left must perform the role of each barrier on the right.

It is interesting to note that the most expensive barrier, StoreLoad, maps to membar, and that it is redundant in all cases except for a volatile store followed by a volatile load!

JMM "volatile" Semantics for sun.misc.Unsafe

The Unsafe class provides a number of methods (mainly used by the java.util.concurrent utilities) that allow for atomic operations on variables with the same memory model semantics as used for Java volatile variables (in some cases there are checks that the field in question was in fact declared as volatile).

Method Description
Unsafe_GetObjectVolatile Loads an object reference field
Unsafe_PutObjectVolatile Stores an object reference field
Unsafe_GetLongVolatile Loads a Java long field
Unsafe_PutLongVolatile Stores a Java long field
Unsafe_Get<type>Volatile Loads a Java <type> field (everything except long and object reference)
Unsafe_Put<type>Volatile Stores a Java <type> field (everything except long and object reference)

For the native implementation (as used by non-compiled code: unsafe.cpp) most of the above are implemented in terms of two macros: GET_FIELD_VOLATILE and SET_FIELD_VOLATILE. For the long case there is further code to check if the VM supports_cx8 which identifies whether the platform supports 64-bit atomic operations (even if a 32-bit platform), and if not, object-locking is used to make the operation atomic - these cases need not be considered further. The Object version uses the macro GET_OOP_FIELD_VOLATILE for "get" and a direct implementation for "put"/"set".

The barriers used by these actions (at least as of fix 7009756 - Jan 8, 2011) are:

Action Generated Sequence
GET_FIELD_VOLATILE OrderAccess::loadAcquire()
PUT_FIELD_VOLATILE OrderAccess::release_store_fence()
GET_OOP_FIELD_VOLATILE load; OrderAccess::acquire()
Unsafe_SetObjectVolatile OrderAccess::release(); store; OrderAccess::fence()

These methods are also intrinsified by the client (C1) and server (C2) compilers - that is, those compilers insert a direct representation of these methods into compiled code.

C1 Unsafe Intrinsics

The intrinic methods are listed in vmSymbols.hpp which maps each Unsafe method to a label:

and in c1_GraphBuilder.cpp these are then mapped to a single operation that is passed the type of the field being operated upon where the final "true" arg indicates these are volatile accesses.

The LIR Generator processes these nodes in LIRGenerator::do_UnsafeGetObject and LIRGenerator::do_UnsafePutObject. Note that the use of the term "Object" is misleading here as it should really be "field".

Those methods, in turn, for the volatile case generate the following code sequences (as of CR 7010665 - Jan 9, 2011):

Action Generated Sequence
do_UnsafeGetObject load; membar_acquire()
do_UnsafePUtObject membar_release(); store; membar()

C2 Unsafe Intrinsics

TBD

Unsafe "Ordered" Methods

In addition to the volatile get/put methods the Unsafe class also supports three more weakly defined operations, but still with ordering constraints:

In terms of memory barrier requirements these methods do not require the trailing membar/fence/storeLoad barriers.

However, both the native implementations in unsafe.cpp and the C1 intrinsic versions, implement these identically to the volatile versions, with no relaxing of the memory barriers. Only the C2 intrinsics actually elide the trailing barrier.