The new reflection - VarHandle fundamentals
An introduction to VarHandle covering motivation, acquisition, and basic access modes.
The new reflection (a 4-part series)
- 1 The new reflection - Basics
- 2 The new reflection - Intermediate use cases
- 3 The new reflection - Advanced method handles
- 4 The new reflection - VarHandle fundamentals
Recap
In the previous posts, we covered method handles in depth: from basic lookup and invocation through advanced composition and control flow combinators.
Now we turn to a closely related abstraction: VarHandle. Where MethodHandle gives you indirect access to methods, VarHandle gives you indirect access to variables - with fine-grained control over memory ordering and atomic operations.
Why VarHandle?
Before Java 9, if you needed anything beyond plain field access, your options were limited and each had significant drawbacks.
volatile
The volatile keyword provides visibility guarantees and prevents reordering, but it's all-or-nothing: every access to a volatile field pays the full cost of sequential consistency. There is no way to perform a plain (cheap) read of a volatile field when you know the ordering guarantee isn't needed, nor is there any way to perform an atomic compare-and-set.
Also, other weaker ordering paradigms like acquire/release are not directly supported by volatile.
java.util.concurrent.atomic
The Atomic* classes (AtomicInteger, AtomicReference, etc.) provide atomic operations, but they require a wrapper object. This adds an extra level of indirection and an extra allocation which is sometimes not acceptable.
sun.misc.Unsafe
Unsafe can do nearly everything: atomic operations, fine-grained ordering, direct memory access. But it is, as the name suggests, unsafe. It performs no type checking, no bounds checking, and no access control. It requires raw field offsets obtained through reflection. And it is an internal API that is not portable and is being progressively restricted, and will eventually be removed.
VarHandle: the safe middle ground
VarHandle, introduced in Java 9, provides the flexibility of Unsafe with the safety of a proper API:
- Type-safe: operations are signature-polymorphic, just like
MethodHandle, so you get compile-time type checking without boxing - Access-controlled: acquisition goes through
Lookup, so access is checked once at creation time - Fine-grained ordering: you choose the memory ordering strength for each individual access
- No wrapper overhead: operates directly on a field, array element, or memory segment
What is a VarHandle?
A VarHandle is a typed, dynamically-checked reference to a variable. A variable, in this context, can be:
- An instance field of a class
- A static field of a class
- An element of an array
- An element of an NIO buffer
- A region of a
MemorySegment(for off-heap or structured memory access on recent JDKs only)
Once you have a VarHandle, you can perform multiple kinds of operations on the same underlying variable. These operations are called access modes, and they range from plain reads and writes all the way to atomic compare-and-set and bitwise operations.
Like MethodHandle, VarHandle uses signature polymorphism. The access mode methods (like get, set, compareAndSet, etc.) accept and return values whose types are determined by the variable being accessed and the specific access mode.
Acquiring a VarHandle
Instance fields via Lookup
The most common way to acquire a VarHandle is through a Lookup. For an instance field:
class Counter {
private int count;
private static final VarHandle countHandle;
static {
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
countHandle = lookup.findVarHandle(Counter.class, "count", int.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
}
The three arguments are the declaring class, the field name, and the field type. As with method handle lookups, the access check happens at creation time.
Avoiding the try/catch with ConstantBootstraps
As we covered in the previous post, ConstantBootstraps.fieldVarHandle provides a cleaner alternative that throws no checked exceptions:
class Counter {
private int count;
private static final VarHandle countHandle = ConstantBootstraps.fieldVarHandle(
MethodHandles.lookup(),
"count",
VarHandle.class,
Counter.class,
int.class
);
}
This is the preferred pattern for field VarHandle acquisition. It eliminates the static initializer block entirely.
Static fields
For static fields, use findStaticVarHandle:
countHandle = lookup.findStaticVarHandle(Counter.class, "globalCount", int.class);
VarHandle instances for static fields do not take a receiver argument in their access mode methods.
Array elements
For array element access, use MethodHandles.arrayElementVarHandle:
VarHandle arrayHandle = MethodHandles.arrayElementVarHandle(int[].class);
An array VarHandle takes two leading parameters in its access modes: the array itself and the index. So in this case, because it is an int[] typed array, get becomes (int[], int)int and set becomes (int[], int, int)void.
Access mode categories
Every VarHandle supports some or all of five categories of access mode. Not every VarHandle supports every mode - for example, a handle to a final field only supports reads.
Some of these operations imply concurrency modes (like "opaque" or "acquire") which are beyond the scope of this post.
Read access
Read-only access modes retrieve the current value of the variable:
get- plain readgetVolatile- volatile readgetOpaque- opaque read (atomicity, but no ordering)getAcquire- acquire read (pairs with a release write)
Write access
Write-only access modes set the value of the variable:
set- plain writesetVolatile- volatile writesetOpaque- opaque writesetRelease- release write (pairs with an acquire read)
Atomic update
These modes atomically read the old value, compare it, and conditionally write a new value:
compareAndSet- returnsboolean(success/failure)compareAndExchange*- returns the witness value (the value that was actually found), with an optional weaker concurrency modeweakCompareAndSet*- may spuriously fail, but can be faster in certain circumstances (see the documentation for details); also supports even more optional weaker concurrency modes
Concurrency algorithms are beyond the scope of this post, but it's worth noting that compareAndExchange has an advantage over compareAndSet and its variants, not only because of the additional concurrency modes available on that method but because it does the compare-and-set and a combined read in one operation:
// read the old value once
String oldVal = myHandle.getVolatile(this);
String newVal = oldVal + " - Something new!";
// give it a try
String witness = myHandle.compareAndExchange(this, oldVal, newVal);
while (witness != oldVal) {
// continue retrying until we do it
oldVal = witness;
newVal = oldVal + " - Something new!";
witness = myHandle.compareAndExchange(this, oldVal, newVal);
}
Numeric atomic update
For numeric variable types (int, long, float, double, and their wrappers), these modes atomically read and modify:
getAndAdd*- atomic addition, returns the old value, with an optional concurrency mode
Bitwise atomic update
For integer variable types (int, long), atomic bitwise operations:
getAndBitwiseOr*,getAndBitwiseAnd*,getAndBitwiseXor*- each returns the old value, with an optional concurrency mode
Each of the atomic modes also comes in Acquire and Release variants.
Checking support
You can check at run time whether a particular access mode is supported:
if (countHandle.isAccessModeSupported(VarHandle.AccessMode.COMPARE_AND_SET)) {
// safe to use compareAndSet on this handle
}
Basic access: plain and volatile
To get started with VarHandle, the two most important access modes are plain and volatile. These correspond to ordinary field access and volatile field access, respectively.
Plain access
Plain access via get and set has the same semantics as reading or writing a non-volatile field. There are no special ordering guarantees beyond what the Java Memory Model provides for normal (non-volatile) accesses.
class Counter {
private int count;
private static final VarHandle countHandle = ConstantBootstraps.fieldVarHandle(
MethodHandles.lookup(), "count", VarHandle.class,
Counter.class, int.class);
public int getCount() {
return (int) countHandle.get(this);
}
public void setCount(int value) {
countHandle.set(this, value);
}
}
Note the signature polymorphism at work: get takes the receiver (this, of type Counter) and returns an int, matching the field type. No boxing, no array wrapping.
Volatile access
Volatile access via getVolatile and setVolatile provides the same guarantees as reading or writing a volatile field: full sequential consistency and a happens-before relationship between a volatile write and a subsequent volatile read of the same variable.
public int getCountVolatile() {
return (int) countHandle.getVolatile(this);
}
public void setCountVolatile(int value) {
countHandle.setVolatile(this, value);
}
One key insight is that the same field - declared without the volatile keyword - can be accessed with different ordering strengths depending on the situation.
Next...
In the final post of this series, we'll bring VarHandle and MethodHandle together by showing how to extract method handles from a VarHandle and compose them using the combinators we covered earlier.