JEP 193: Variable Handles

AuthorDoug Lea
OwnerPaul Sandoz
Created2014/01/06 20:00
Updated2015/09/01 11:39
TypeFeature
StatusTargeted
Componentcore-libs
ScopeSE
JSRTBD
Discussioncore dash libs dash dev at openjdk dot java dot net
EffortM
DurationL
Priority2
Reviewed byDave Dice, Paul Sandoz
Endorsed byBrian Goetz
Release9
Issue8046183
DependsJEP 188: Java Memory Model Update

Summary

Define a standard means to invoke the equivalents of java.util.concurrent.atomic and sun.misc.Unsafe operations upon object fields and array elements.

Define a standard set of fence operations for fine-grained control of memory ordering (sun.misc.Unsafe provides a non-standard set of fence operations).

Define a standard reachability fence operation ensuring a referenced object remains strongly reachable.

Goals

The following are required goals:

It is desirable, but not required, that the API be as good as the java.util.concurrent.atomic API.

Motivation

As concurrent and parallel programming in Java continue to expand, programmers are increasingly frustrated by not being able to use Java constructs to arrange atomic or ordered operations on the fields of individual classes; for example, atomically incrementing a count field. Until now the only ways to achieve these effects were to use a stand-alone AtomicInteger (adding both space overhead and additional concurrency issues to manage indirection) or, in some situations, to use atomic FieldUpdaters (often encountering more overhead than the operation itself), or to use the unsafe (and unportable and unsupported) sun.misc.Unsafe API for JVM intrinsics. Intrinsics are faster, so they have become widely used, to the detriment of safety and portability.

Without this JEP, these problems are expected to become worse as atomic APIs expand to cover additional access-consistency policies (aligned with the recent C++11 memory model) as part of Java Memory Model revisions.

Description

A variable handle is a typed reference to a variable, which supports read and write access to the variable under a variety of access modes. Supported variable kinds include instance fields, static fields and array elements. Other variable kinds are being considered and may be supported such as array views, viewing a byte or char array as a long array, and locations in off-heap regions described by ByteBuffers.

Variable handles require library enhancements, JVM enhancements, and compiler support. Additionally, it requires minor updates to the Java Language Specification and the Java Virtual Machine Specification. Minor language enhancements, that enhance compile-time type checking and complement existing syntax, are also considered.

The resulting specifications are expected to be extensible in natural ways to additional primitive-like value types or additional array-like types, if they are ever added to Java. This is not, however, a general-purpose transaction mechanism for controlling accesses and updates to multiple variables. Alternative forms for expressing and implementing such constructs may be explored in the course of this JEP, and may be the subject of further JEPs.

Variable handles are modelled by a single abstract class, java.lang.invoke.VarHandle, where each variable access mode is represented by a signature-polymorphic method:

abstract class VarHandle {
    // Load

    /**
     * Returns the value, with memory semantics of reading a
     * non-volatile variable.
     *
     * @return the value
     */
    public final native
    @MethodHandle.PolymorphicSignature
    Object get(Object... args);

    /**
     * Returns the value, with memory semantics of reading a volatile
     * variable.
     *
     */
    public final native
    @MethodHandle.PolymorphicSignature
    Object getVolatile(Object... args);

    /**
     * Returns the value, and ensures that subsequent loads and stores
     * are not reordered before this access.
     *
     * @apiNote Ignoring the many semantic differences from C and
     * C++, this method has memory ordering effects compatible with
     * memory_order_acquire ordering.
     *
     */
    public final native
    @MethodHandle.PolymorphicSignature
    Object getAcquire(Object... args);

    /**
     * Returns the value, accessed in program order, but with no
     * assurance of memory ordering effects with respect to other
     * threads.
     *
     * @return the value
     */
    public final native
    @MethodHandle.PolymorphicSignature
    Object getOpaque(Object... args);

    // Store

    /**
     * Sets the value, with memory semantics of setting a
     * non-volatile, non-final variable.
     *
     */
    public final native
    @MethodHandle.PolymorphicSignature
    void set(Object... args);

    /**
     * Sets the value, with memory semantics of setting a volatile
     * variable.
     *
     * @apiNote Ignoring the many semantic differences from C and
     * C++, this method has memory ordering effects compatible with
     * memory_order_seq_cst.
     *
     */
    public final native
    @MethodHandle.PolymorphicSignature
    void setVolatile(Object... args);

    /**
     * Sets the value, and ensures that prior loads and stores are not
     * reordered after this access.
     *
     * @apiNote Ignoring the many semantic differences from C and
     * C++, this method has memory ordering effects compatible with
     * memory_order_release ordering.
     *
     */
    public final native
    @MethodHandle.PolymorphicSignature
    void setRelease(Object... args);

    /**
     * Sets the value, in program order, but with no assurance of
     * memory ordering effects with respect to other threads.
     *
     */
    public final native
    @MethodHandle.PolymorphicSignature
    void setOpaque(Object... args);

    // CAS

    /**
     * Atomically sets the value to the given updated value with the
     * memory semantics of setVolatile if the current value {@code ==}
     * the expected value, as accessed with the memory semantics of
     * getVolatile.
     *
     */
    public final native
    @MethodHandle.PolymorphicSignature
    boolean compareAndSet(Object... args);

    // Value-returning compare and exchange

    /**
     * Atomically sets the value to the given updated value with the
     * memory semantics of setVolatile if the current value {@code ==}
     * the expected value, as accessed with the memory semantics of
     * getVolatile.
     *
     */
    public final native
    @MethodHandle.PolymorphicSignature
    Object compareAndExchangeVolatile(Object... args);

    /**
     * Atomically sets the value to the given updated value with the
     * memory semantics of setting a non-volatile variable if the
     * current value {@code ==} the expected value, as accessed with
     * the memory semantics of getAcquire.
     *
     */
    public final native
    @MethodHandle.PolymorphicSignature
    Object compareAndExchangeAcquire(Object... args);

    /**
     * Atomically sets the value to the given updated value with the
     * memory semantics of setRelease if the current value {@code ==}
     * the expected value, as accessed with the memory semantics of
     * reading a non-volatile variable.
     *
     */
    public final native
    @MethodHandle.PolymorphicSignature
    Object compareAndExchangeRelease(Object... args);

    // Weak (spurious failures allowed) 

    /**
     * Possibly atomically sets the value to the given updated value
     * with the semantics of setting a non-volatile variable if the
     * current value {@code ==} the expected value, as accessed with
     * the memory semantics of reading a non-volatile variable.  This
     * operation may fail spuriously (typically, due to memory
     * contention) even if the current value does match the expected
     * value.
     * 
     */
    public final native
    @MethodHandle.PolymorphicSignature
    boolean weakCompareAndSet(Object... args);

    /**
     * Possibly atomically sets the value to the given updated value
     * with the memory semantics of setting a non-volatile variable if
     * the current value {@code ==} the expected value, as as accessed
     * with the memory semantics of getAcquire.  This operation may
     * fail spuriously (typically, due to memory contention) even if
     * the current value does match the expected value.
     * 
     */
    public final native
    @MethodHandle.PolymorphicSignature
    boolean weakCompareAndSetAcquire(Object... args);

    /**
     * Possibly atomically sets the value to the given updated value
     * with the memory semantics of setRelease if the current value
     * {@code ==} the expected value, as as accessed with the memory
     * semantics of reading a non-volatile variable.  This operation
     * may fail spuriously (typically, due to memory contention) even
     * if the current value does match the expected value.
     * 
     */
    public final native
    @MethodHandle.PolymorphicSignature
    boolean weakCompareAndSetRelease(Object... args);

    // special RMW

    /**
     * Atomically sets to the given value with the memory semantics of
     * setVolatile and returns the previous value, as accessed with
     * the memory semantics of getVolatile.
     *
     */
    public final native
    @MethodHandle.PolymorphicSignature
    Object getAndSet(Object... args);

    /**
     * Atomically adds the given value to the current value with the
     * memory semantics of setVolatile, and returns the previous
     * value, as accessed with the memory semantics of getVolatile.    
     *
     */
    public final native
    @MethodHandle.PolymorphicSignature
    Object getAndAdd(Object... args);

    /**
     * Atomically adds the given value to the current value with the
     * memory semantics of setVolatile, and returns the new value,
     * as accessed with the memory semantics of getVolatile.
     *
     */
    public final native
    @MethodHandle.PolymorphicSignature
    Object addAndGet(Object... args);
}    

The set of access modes represents a minimal viable set and are designed to be compatible with C/C++11 atomics without depending on a revised update to the Java Memory Model. Additional access modes will be added if required. Some access modes may not be applicable for certain variable types and, if so, when invoked on an associated VarHandle instance will throw an UnsupportedOperationException.

The signature-polymorphic characteristic of the access mode methods enables variable handles to support many variable kinds and variable types using just one abstract class. This avoids an explosion of variable kind and type-specific classes. Furthermore, even though the access mode method signatures are declared as a variable argument array of Object, such signature-polymorphic characteristics ensure there will be no boxing of primitive value arguments and no packing of arguments into an array. This enables predictable behaviour and performance at runtime for the HotSpot interpreter and C1/C2 compilers.

Methods to create VarHandle instances are located in the same area as that to produce MethodHandle instances which access equivalent or similar variable kinds.

Methods to create VarHandle instances for instance and static field variable kinds are located in java.lang.invoke.MethodHandles.Lookup and are created by a process of looking up the field within the associated receiving class. For example, such lookup to obtain a VarHandle for a field named i of type int on a receiver class Foo might be performed as follows:

class Foo {
    int i;

    ...
}

...

class Bar {
    static final VarHandle VH_FOO_FIELD_I;

    static {
        try {
            VH_FOO_FIELD_I = MethodHandles.lookup().
                in(Foo.class).
                findFieldVarHandle(Foo.class, "i", int.class);
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}

The lookup of a VarHandle that accesses a field will, before producing and returning the VarHandle, perform the exact same access control checks (on behalf of the lookup class) as those performed by the lookup up of a MethodHandle that gives read and write access to that same field (see the find{,Static}{Getter,Setter} methods in the MethodHandles.Lookup class).

Access mode methods will throw UnsupportedOperationException when invoked under the following conditions:

A field need not be marked as volatile for an associated VarHandle to perform volatile access. In effect, the volatile modifier, if present, is ignored. This is different to the behaviour of java.util.concurrent.atomic.Atomic{Int, Long, Reference}FieldUpdater where corresponding fields have to be marked as volatile. This can be too restrictive in certain cases where it is known certain volatile accesses are not always required.

Methods to create VarHandle instances for array-based variable types are located in java.lang.invoke.MethodHandles (see the arrayElement{Getter, Setter} methods in the MethodHandles class). For example, a VarHandle to an array of int may be created as follows:

VarHandle intArrayHandle = MethodHandles.arrayElementVarHandle(int[].class);

Access mode methods will throw UnsupportedOperationException when invoked under the following conditions:

All primitive types and references types are supported for the variable type of variable kinds that are instance fields, static fields and array elements. Other variable kinds may support all or a subset of those types.

Methods to create VarHandle instances for array-view-based variable types are also located in java.lang.invoke.MethodHandles. For example, a VarHandle to view an array of byte as an unaligned array of long may be created as follows:

VarHandle longArrayViewHandle = MethodHandles.arrayElementViewVarHandle(
        byte[].class, long[].class, true);

Although similar mechanisms can be achieved using java.nio.ByteBuffer, it requires that a ByteBuffer instance be created wrapping a byte array. This does not always guarantee reliable performance due to the fragility of escape-analysis and that accesses have to go through the ByteBuffer instance. In the case of unaligned access all but the relaxed access mode methods will throw UnsupportedOperationException. For aligned access (passing false instead of true) certain volatile operations, depending on the variable type are possible. Such VarHandle instances may be utilized to vectorize array access.

The number of arguments, the argument types, and return type of access mode methods are governed by variable kind, the variable type and the characteristics of the access mode. VarHandle creation methods (such as those previously described) will document the requirements. For example, a compareAndSet on the previously-looked up VH_FOO_FIELD_I handle requires 3 arguments, an instance of receiver Foo and two ints for the expected and actual values:

Foo f = ...
boolean r = VH_FOO_FIELD_I.compareAndSet(f, 0, 1);

In contrast, a getAndSet requires 2 arguments, an instance of receiver Foo and one int that is the value to be set:

int o = (int) VH_FOO_FIELD_I.getAndSet(f, 2);

Access to array elements will require an additional argument, of type int, between the receiver and value arguments (if any), that corresponds to the array index of the element to be operated upon.

For predictable behaviour and performance at runtime VarHandle instances should be held in static final fields (as required for instances of Atomic{Int, Long, Reference}FieldUpdater). This ensures that constant folding will occur for access mode method invocations, such as folding away method signature checks and/or argument cast checks.

Note: Future HotSpot enhancements might support constant folding for VarHandle, or MethodHandle, instances held in non-static final fields, method arguments, or local variables.

A MethodHandle may be produced for a VarHandle access mode method by using MethodHandles.Lookup.findVirtual. For example, to produce a MethodHandle to the "compareAndSet" access mode for a particular variable kind and type:

Foo f = ...
MethodHandle mhToVhCompareAndSet = MethodHandles.publicLookup().findVirtual(
        VarHandle.class,
        "compareAndSet",
        MethodType.methodType(boolean.class, Foo.class, int.class, int.class));

The MethodHandle can then be invoked with a variable kind and type compatible VarHandle instance as the first parameter:

mhToVhCompareAndSet.invokeExact(VH_FOO_FIELD_I, f, 0, 1);

Or mhToVhCompareAndSet can be bound to the VarHandle instance and then invoked:

MethodHandle mhToBoundVhCompareAndSet = mhToVhCompareAndSet
        .bindTo(VH_FOO_FIELD_I);
mhToBoundVhCompareAndSet.invokeExact(f, 0, 1);

Additional MethodHandle production methods may be considered such as MethodHandles.varHandleInvoker which is the analogue of MethodHandles.exactInvoker, or a method on VarHandle to return a bound MethodHandle.

The source compilation of an access mode method invocation will follow the same rules as for signature-polymorphic method invocation to MethodHandle.invokeExact and MethodHandle.invoke. The following additions will be required to the Java Language Specification:

  1. Make reference to the signature-polymorphic access mode methods in the VarHandle class.
  2. Allow signature-polymorphic methods to return types other than Object, indicating that the return type is not polymorphic (and would otherwise be declared via a cast at the call site). This makes it easier invoke write-based access methods that return void and invoke compareAndSet that returns a boolean value.

It would be desirable, but not a requirement, that source compilation of a signature-polymorphic method invocation be enhanced to perform target typing of the polymorphic return type such that an explicit cast is not required.

Note: a syntax and runtime support for looking up a MethodHandle or a VarHandle leveraging the syntax of method references, such as VarHandle VH_FOO_FIELD_I = Foo::i is desirable but not in scope for this JEP.

The runtime invocation of an access mode method invocation will follow similar rules as for signature-polymorphic method invocation to MethodHandle.invokeExact and MethodHandle.invoke. The following additions will be required to the Java Virtual Machine Specification:

  1. Make reference to the signature-polymorphic access mode methods in the VarHandle class.
  2. Specify invokevirtual byte code behaviour of invocation to access mode signature-polymorphic methods. It is anticipated that such behaviour can be specified by defining a transformation from the access mode method invocation to a MethodHandle which is then invoked using invokeExact with the same parameters (see previous use of MethodHandles.Lookup.findVirtual). Further investigation is required to determine if refined transformations are possible (now, or in the future without breaking compatibility) to make access mode method similar to MethodHandle.invoke, for example, by transforming the method handle via MethodHandle.asType where all reference types are cast from Object.

It is important that the VarHandle implementations for the supported variable kinds, types and access modes are reliably efficient and meet the performance goals. Leveraging signature-polymorphic methods helps in terms of avoiding boxing and array packing. Implementations will:

A couple of HotSpot intrinsics are necessary, some of which are enumerated as folllows:

In addition further improvements to range checks by HotSpot have been implemented (JDK-8073480) or are needed (JDK-8003585 to strength reduce range checks in say the fork/join framework or in say HashMap or ConcurrentHashMap). The VarHandle implementations should have minimal dependencies on other classes within the java.lang.invoke package to avoid increasing startup time and to avoid cyclic dependencies occurring during static initialization. For example, ConcurrentHashMap is used by such classes and if ConcurrentHashMap is modified to use VarHandles it needs to be ensured no cyclic dependencies are introduced. Furthermore, it is desirable that the C2 HotSpot compilation time is not unduly increased for methods containing VarHandle method invocations.

Memory fences

Fenced operations are defined in a Fences class and represents a minimal viable set:

/**
 * A set of methods providing fine-grained control of memory ordering.
 *
 * <p>The Java Language Specification permits operations to be
 * executed in orders different than are apparent in program source
 * code, subject to constraints mainly arising from the use of locks
 * and volatile fields. The methods of this class can also be used to
 * impose constraints. Their specifications are phrased in terms of
 * the lack of "reorderings" -- observable ordering effects that might
 * otherwise occur if the fence were not present.
 *
 * @apiNote More precise phrasing of these specifications may
 * accompany future updates of the Java Language Specification.
*/
public class Fences {

   /**
    * Ensures that loads and stores before the fence will not be
    * reordered with loads and stores after the fence.
    *
    * @apiNote Ignoring the many semantic differences from C and
    * C++, this method has memory ordering effects compatible with
    * atomic_thread_fence(memory_order_seq_cst)
    */
   public static void fullFence() {}

   /**
    * Ensures that loads before the fence will not be reordered with
    * loads and stores after the fence.
    *
    * @apiNote Ignoring the many semantic differences from C and
    * C++, this method has memory ordering effects compatible with
    * atomic_thread_fence(memory_order_acquire)
    */
   public static void acquireFence() {}

   /**
    * Ensures that loads and stores before the fence will not be
    * reordered with stores after the fence.
    *
    * @apiNote Ignoring the many semantic differences from C and
    * C++, this method has memory ordering effects compatible with
    * atomic_thread_fence(memory_order_release)
    */
   public static void releaseFence() {}

   /**
    * Ensures that loads before the fence will not be reordered with
    * loads after the fence.
    */
   public static void loadLoadFence() {}

   /**
    * Ensures that stores before the fence will not be reordered with
    * stores after the fence.
    */
   public static void storeStoreFence() {}

}

A full fence is stronger (in terms of ordering guarantees) than an acquire fence which is stronger than a loadLoad fence. Likewise a full fence is stronger than a release fence which is stronger than a storeStore fence.

It is still to be determined under which package this class should reside. A natural place is under the java.lang.concurrent package, but it could also reside under java.lang. The latter being advantageous in that the static methods could be natively defined, rather than having to define such methods on an internal class, such as sun.misc.Unsafe, which the public static methods defer to.

Reachability fence

The reachability fence will be defined as a static method on java.lang.ref.Reference:

class java.lang.ref.Reference {
   // add:

   /**
    * Ensures that the object referenced by the given reference
    * remains <em>strongly reachable</em> (as defined in the {@link
    * java.lang.ref} package documentation), regardless of any prior
    * actions of the program that might otherwise cause the object to
    * become unreachable; thus, the referenced object is not
    * reclaimable by garbage collection at least until after the
    * invocation of this method. Invocation of this method does not
    * itself initiate garbage collection or finalization.
    *
    * @param ref the reference. If null, this method has no effect.
    */
   public static void reachabilityFence(Object ref) {}

}

See JDK-8133348 for a proposed implementation.

It is currently out of scope to provide an annotation, @Finalized say, to be declared on a method, which at either compile or runtime results in as if the method body was wrapped as follows:

try {
    <method body>
} finally {
    Reference.reachabilityFence(this);
}

It is anticipated that such functionality could be supported by a compile-time annotation processor.

Alternatives

Introducing new forms of "value type" were considered that support volatile operations. However, this would be inconsistent with properties of other types, and would also require more effort for programmers to use. Reliance upon java.util.concurrent.atomic FieldUpdaters was also considered, but their dynamic overhead and usage limitations make them unsuitable.

Several other alternatives, including those based on field references, have been raised and dismissed as unworkable on syntactic, efficiency, and/or usability grounds over the many years that these issues have been discussed.

Syntax enhancements were considered in a previous version of this JEP but were deemed too "magical", with the overloaded use of the volatile keyword scoping to floating interfaces, one for references and one for each supported primitive type.

Generic types extending from VarHandle were considered in a previous version of this JEP but such an addition, with enhanced polymorphic signatures for generic types and special treatment of boxed type variables, was considered immature given a future Java release with value types and generics over primitives with JEP 218, and improved arrays with Arrays 2.0.

An implementation-specific invokedynamic approach was also considered in a previous version of this JEP. This required that compiled method calls with and without invokedynamic were carefully aligned to be the same in terms of semantics. In addition the use of invokedynamic in core classes such as say ConcurrentHashMap will result in cyclic dependencies.

Testing

Stress tests will be developed using the jcstress harness.

Risks and Assumptions

A prototype implementation of VarHandle has been performance-tested with nano-benchmarks and fork/join benchmarks, where the fork/join library's use of sun.misc.Unsafe was replaced with VarHandle. No major performance issues have been observed so far, and the HotSpot compiler issues identified do not seem onerous (folding cast checks and improving array bounds checks). We are therefore confident of the feasibility of this approach. However, we expect that it will require more experimentation to ensure the compilation techniques are reliable in the performance-critical contexts where these constructs are most often needed.

Dependences

The classes in java.util.concurrent (and other areas identified in the JDK) will be migrated from sun.misc.Unsafe to VarHandle.

This JEP does not depend on JEP 188: Java Memory Model Update.