8291065: Creating a VarHandle for a static field triggers class initialization

Reviewed-by: mchung, psandoz
This commit is contained in:
Chen Liang 2023-07-18 00:58:25 +00:00 committed by Mandy Chung
parent a53345ad03
commit 201e3bcf52
10 changed files with 744 additions and 160 deletions

View File

@ -68,6 +68,7 @@ import java.util.function.BiFunction;
}
@Override
@ForceInline
VarHandle asDirect() {
return directTarget;
}
@ -75,8 +76,8 @@ import java.util.function.BiFunction;
@Override
public VarHandle withInvokeExactBehavior() {
return hasInvokeExactBehavior()
? this
: new IndirectVarHandle(target, value, coordinates, handleFactory, vform, true);
? this
: new IndirectVarHandle(target, value, coordinates, handleFactory, vform, true);
}
@Override
@ -86,6 +87,7 @@ import java.util.function.BiFunction;
: new IndirectVarHandle(target, value, coordinates, handleFactory, vform, false);
}
@Override
@ForceInline
boolean checkAccessModeThenIsDirect(VarHandle.AccessDescriptor ad) {
super.checkAccessModeThenIsDirect(ad);
@ -103,9 +105,4 @@ import java.util.function.BiFunction;
MethodHandle targetHandle = target.getMethodHandle(mode); // might throw UOE of access mode is not supported, which is ok
return handleFactory.apply(AccessMode.values()[mode], targetHandle);
}
@Override
public MethodHandle toMethodHandle(AccessMode accessMode) {
return getMethodHandle(accessMode.ordinal()).bindTo(directTarget);
}
}

View File

@ -0,0 +1,136 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
package java.lang.invoke;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Stable;
import java.util.Optional;
import static java.lang.invoke.MethodHandleStatics.UNSAFE;
import static java.lang.invoke.MethodHandleStatics.uncaughtException;
/**
* A lazy initializing var handle. It lazily initializes the referenced class before
* any invocation of the target var handle to prevent reading uninitialized static
* field values.
*/
final class LazyInitializingVarHandle extends VarHandle {
// Implementation notes:
// We put a barrier on both target() (for VH form impl direct invocation)
// and on getMethodHandle() (for indirect VH invocation, toMethodHandle)
private final VarHandle target;
private final Class<?> refc;
private @Stable boolean initialized;
LazyInitializingVarHandle(VarHandle target, Class<?> refc) {
super(target.vform, target.exact);
this.target = target;
this.refc = refc;
}
@Override
MethodType accessModeTypeUncached(AccessType at) {
return target.accessModeType(at.ordinal());
}
@Override
@ForceInline
VarHandle asDirect() {
return target;
}
@Override
@ForceInline
VarHandle target() {
ensureInitialized();
return target;
}
@Override
public VarHandle withInvokeExactBehavior() {
if (!initialized && hasInvokeExactBehavior())
return this;
var exactTarget = target.withInvokeExactBehavior();
return initialized ? exactTarget : new LazyInitializingVarHandle(exactTarget, refc);
}
@Override
public VarHandle withInvokeBehavior() {
if (!initialized && !hasInvokeExactBehavior())
return this;
var nonExactTarget = target.withInvokeBehavior();
return initialized ? nonExactTarget : new LazyInitializingVarHandle(nonExactTarget, refc);
}
@Override
public Optional<VarHandleDesc> describeConstable() {
return target.describeConstable();
}
@Override
public MethodHandle getMethodHandleUncached(int accessMode) {
var mh = target.getMethodHandle(accessMode);
if (this.initialized)
return mh;
return MethodHandles.collectArguments(mh, 0, ensureInitializedMh()).bindTo(this);
}
@ForceInline
private void ensureInitialized() {
if (this.initialized)
return;
initialize();
}
private void initialize() {
UNSAFE.ensureClassInitialized(refc);
this.initialized = true;
this.methodHandleTable = target.methodHandleTable;
}
private static @Stable MethodHandle MH_ensureInitialized;
private static MethodHandle ensureInitializedMh() {
var mh = MH_ensureInitialized;
if (mh != null)
return mh;
try {
return MH_ensureInitialized = MethodHandles.lookup().findVirtual(
LazyInitializingVarHandle.class,
"ensureInitialized",
MethodType.methodType(void.class));
} catch (Throwable ex) {
throw uncaughtException(ex);
}
}
}

View File

@ -4201,7 +4201,7 @@ return mh1;
}
refc = lookupClass();
}
return VarHandles.makeFieldHandle(getField, refc, getField.getFieldType(),
return VarHandles.makeFieldHandle(getField, refc,
this.allowedModes == TRUSTED && !getField.isTrustedFinalField());
}
/** Check access and get the requested constructor. */

View File

@ -472,7 +472,8 @@ import static java.lang.invoke.MethodHandleStatics.UNSAFE;
* @since 9
*/
public abstract sealed class VarHandle implements Constable
permits IndirectVarHandle, VarHandleSegmentViewBase,
permits IndirectVarHandle, LazyInitializingVarHandle,
VarHandleSegmentViewBase,
VarHandleByteArrayAsChars.ByteArrayViewVarHandle,
VarHandleByteArrayAsDoubles.ByteArrayViewVarHandle,
VarHandleByteArrayAsFloats.ByteArrayViewVarHandle,
@ -518,10 +519,23 @@ public abstract sealed class VarHandle implements Constable
this.exact = exact;
}
RuntimeException unsupported() {
return new UnsupportedOperationException();
/**
* Returns the target VarHandle. Subclasses may override this method to implement
* additional logic for example lazily initializing the declaring class of a static field var handle.
*/
@ForceInline
VarHandle target() {
return asDirect();
}
/**
* Returns the direct target VarHandle. Indirect VarHandle subclasses should implement
* this method.
*
* @see #getMethodHandle(int)
* @see #checkAccessModeThenIsDirect(AccessDescriptor)
*/
@ForceInline
VarHandle asDirect() {
return this;
}
@ -2062,13 +2076,19 @@ public abstract sealed class VarHandle implements Constable
/**
* Validates that the given access descriptors method type matches up with
* the access mode of this VarHandle, then returns if this is a direct
* method handle. These operations were grouped together to slightly
* the access mode of this VarHandle, then returns if this is direct.
* These operations were grouped together to slightly
* improve efficiency during startup/warmup.
*
* A direct VarHandle's VarForm has implementation MemberNames that can
* be linked directly. If a VarHandle is indirect, it must override
* {@link #isAccessModeSupported} and {@link #getMethodHandleUncached}
* which access MemberNames.
*
* @return true if this is a direct VarHandle, false if it's an indirect
* VarHandle.
* @throws WrongMethodTypeException if there's an access type mismatch
* @see #asDirect()
*/
@ForceInline
boolean checkAccessModeThenIsDirect(VarHandle.AccessDescriptor ad) {
@ -2144,7 +2164,7 @@ public abstract sealed class VarHandle implements Constable
public MethodHandle toMethodHandle(AccessMode accessMode) {
if (isAccessModeSupported(accessMode)) {
MethodHandle mh = getMethodHandle(accessMode.ordinal());
return mh.bindTo(this);
return mh.bindTo(asDirect());
}
else {
// Ensure an UnsupportedOperationException is thrown
@ -2186,6 +2206,14 @@ public abstract sealed class VarHandle implements Constable
return mh;
}
/**
* Computes a method handle that can be passed the {@linkplain #asDirect() direct}
* var handle of this var handle with the given access mode. Pre/postprocessing
* such as argument or return value filtering should be done by the returned
* method handle.
*
* @throws UnsupportedOperationException if the access mode is not supported
*/
MethodHandle getMethodHandleUncached(int mode) {
MethodType mt = accessModeType(AccessMode.values()[mode]).
insertParameterTypes(0, VarHandle.class);
@ -2401,13 +2429,13 @@ public abstract sealed class VarHandle implements Constable
public VarHandle resolveConstantDesc(MethodHandles.Lookup lookup)
throws ReflectiveOperationException {
return switch (kind) {
case FIELD -> lookup.findVarHandle((Class<?>) declaringClass.resolveConstantDesc(lookup),
case FIELD -> lookup.findVarHandle(declaringClass.resolveConstantDesc(lookup),
constantName(),
(Class<?>) varType.resolveConstantDesc(lookup));
case STATIC_FIELD -> lookup.findStaticVarHandle((Class<?>) declaringClass.resolveConstantDesc(lookup),
varType.resolveConstantDesc(lookup));
case STATIC_FIELD -> lookup.findStaticVarHandle(declaringClass.resolveConstantDesc(lookup),
constantName(),
(Class<?>) varType.resolveConstantDesc(lookup));
case ARRAY -> MethodHandles.arrayElementVarHandle((Class<?>) declaringClass.resolveConstantDesc(lookup));
varType.resolveConstantDesc(lookup));
case ARRAY -> MethodHandles.arrayElementVarHandle(declaringClass.resolveConstantDesc(lookup));
default -> throw new InternalError("Cannot reach here");
};
}

View File

@ -53,9 +53,10 @@ final class VarHandles {
}
};
static VarHandle makeFieldHandle(MemberName f, Class<?> refc, Class<?> type, boolean isWriteAllowedOnFinalFields) {
static VarHandle makeFieldHandle(MemberName f, Class<?> refc, boolean isWriteAllowedOnFinalFields) {
if (!f.isStatic()) {
long foffset = MethodHandleNatives.objectFieldOffset(f);
Class<?> type = f.getFieldType();
if (!type.isPrimitive()) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleReferences.FieldInstanceReadOnly(refc, foffset, type)
@ -106,66 +107,65 @@ final class VarHandles {
}
}
else {
// TODO This is not lazy on first invocation
// and might cause some circular initialization issues
// Replace with something similar to direct method handles
// where a barrier is used then elided after use
if (UNSAFE.shouldBeInitialized(refc))
UNSAFE.ensureClassInitialized(refc);
Class<?> decl = f.getDeclaringClass();
Object base = MethodHandleNatives.staticFieldBase(f);
long foffset = MethodHandleNatives.staticFieldOffset(f);
if (!type.isPrimitive()) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleReferences.FieldStaticReadOnly(decl, base, foffset, type)
: new VarHandleReferences.FieldStaticReadWrite(decl, base, foffset, type));
}
else if (type == boolean.class) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleBooleans.FieldStaticReadOnly(decl, base, foffset)
: new VarHandleBooleans.FieldStaticReadWrite(decl, base, foffset));
}
else if (type == byte.class) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleBytes.FieldStaticReadOnly(decl, base, foffset)
: new VarHandleBytes.FieldStaticReadWrite(decl, base, foffset));
}
else if (type == short.class) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleShorts.FieldStaticReadOnly(decl, base, foffset)
: new VarHandleShorts.FieldStaticReadWrite(decl, base, foffset));
}
else if (type == char.class) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleChars.FieldStaticReadOnly(decl, base, foffset)
: new VarHandleChars.FieldStaticReadWrite(decl, base, foffset));
}
else if (type == int.class) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleInts.FieldStaticReadOnly(decl, base, foffset)
: new VarHandleInts.FieldStaticReadWrite(decl, base, foffset));
}
else if (type == long.class) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleLongs.FieldStaticReadOnly(decl, base, foffset)
: new VarHandleLongs.FieldStaticReadWrite(decl, base, foffset));
}
else if (type == float.class) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleFloats.FieldStaticReadOnly(decl, base, foffset)
: new VarHandleFloats.FieldStaticReadWrite(decl, base, foffset));
}
else if (type == double.class) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleDoubles.FieldStaticReadOnly(decl, base, foffset)
: new VarHandleDoubles.FieldStaticReadWrite(decl, base, foffset));
}
else {
throw new UnsupportedOperationException();
}
var vh = makeStaticFieldVarHandle(decl, f, isWriteAllowedOnFinalFields);
return maybeAdapt(UNSAFE.shouldBeInitialized(decl)
? new LazyInitializingVarHandle(vh, decl)
: vh);
}
}
static VarHandle makeStaticFieldVarHandle(Class<?> decl, MemberName f, boolean isWriteAllowedOnFinalFields) {
Object base = MethodHandleNatives.staticFieldBase(f);
long foffset = MethodHandleNatives.staticFieldOffset(f);
Class<?> type = f.getFieldType();
if (!type.isPrimitive()) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleReferences.FieldStaticReadOnly(decl, base, foffset, type)
: new VarHandleReferences.FieldStaticReadWrite(decl, base, foffset, type));
}
else if (type == boolean.class) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleBooleans.FieldStaticReadOnly(decl, base, foffset)
: new VarHandleBooleans.FieldStaticReadWrite(decl, base, foffset));
}
else if (type == byte.class) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleBytes.FieldStaticReadOnly(decl, base, foffset)
: new VarHandleBytes.FieldStaticReadWrite(decl, base, foffset));
}
else if (type == short.class) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleShorts.FieldStaticReadOnly(decl, base, foffset)
: new VarHandleShorts.FieldStaticReadWrite(decl, base, foffset));
}
else if (type == char.class) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleChars.FieldStaticReadOnly(decl, base, foffset)
: new VarHandleChars.FieldStaticReadWrite(decl, base, foffset));
}
else if (type == int.class) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleInts.FieldStaticReadOnly(decl, base, foffset)
: new VarHandleInts.FieldStaticReadWrite(decl, base, foffset));
}
else if (type == long.class) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleLongs.FieldStaticReadOnly(decl, base, foffset)
: new VarHandleLongs.FieldStaticReadWrite(decl, base, foffset));
}
else if (type == float.class) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleFloats.FieldStaticReadOnly(decl, base, foffset)
: new VarHandleFloats.FieldStaticReadWrite(decl, base, foffset));
}
else if (type == double.class) {
return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields
? new VarHandleDoubles.FieldStaticReadOnly(decl, base, foffset)
: new VarHandleDoubles.FieldStaticReadWrite(decl, base, foffset));
}
else {
throw new UnsupportedOperationException();
}
}
@ -731,16 +731,7 @@ final class VarHandles {
// Object getAndUpdate(Object value);
// }
//
// static class HandleType {
// final Class<?> receiver;
// final Class<?>[] intermediates;
// final Class<?> value;
//
// HandleType(Class<?> receiver, Class<?> value, Class<?>... intermediates) {
// this.receiver = receiver;
// this.intermediates = intermediates;
// this.value = value;
// }
// record HandleType(Class<?> receiver, Class<?> value, Class<?>... intermediates) {
// }
//
// /**
@ -816,10 +807,8 @@ final class VarHandles {
// List<Class<?>> params = new ArrayList<>();
// if (receiver != null)
// params.add(receiver);
// for (int i = 0; i < intermediates.length; i++) {
// params.add(intermediates[i]);
// }
// for (Parameter p : m.getParameters()) {
// java.util.Collections.addAll(params, intermediates);
// for (var p : m.getParameters()) {
// params.add(value);
// }
// return MethodType.methodType(returnType, params);
@ -828,7 +817,7 @@ final class VarHandles {
// static String generateMethod(MethodType mt) {
// Class<?> returnType = mt.returnType();
//
// LinkedHashMap<String, Class<?>> params = new LinkedHashMap<>();
// var params = new java.util.LinkedHashMap<String, Class<?>>();
// params.put("handle", VarHandle.class);
// for (int i = 0; i < mt.parameterCount(); i++) {
// params.put("arg" + i, mt.parameterType(i));
@ -841,7 +830,7 @@ final class VarHandles {
// String SIGNATURE = getSignature(mt);
// String PARAMS = params.entrySet().stream().
// map(e -> className(e.getValue()) + " " + e.getKey()).
// collect(joining(", "));
// collect(java.util.stream.Collectors.joining(", "));
// String METHOD = GUARD_METHOD_SIG_TEMPLATE.
// replace("<RETURN>", RETURN).
// replace("<NAME>", NAME).
@ -851,12 +840,10 @@ final class VarHandles {
// // Generate method
// params.remove("ad");
//
// List<String> LINK_TO_STATIC_ARGS = params.keySet().stream().
// collect(toList());
// List<String> LINK_TO_STATIC_ARGS = new ArrayList<>(params.keySet());
// LINK_TO_STATIC_ARGS.add("handle.vform.getMemberName(ad.mode)");
//
// List<String> LINK_TO_INVOKER_ARGS = params.keySet().stream().
// collect(toList());
// List<String> LINK_TO_INVOKER_ARGS = new ArrayList<>(params.keySet());
// LINK_TO_INVOKER_ARGS.set(0, LINK_TO_INVOKER_ARGS.get(0) + ".asDirect()");
//
// RETURN = returnType == void.class
@ -884,10 +871,8 @@ final class VarHandles {
// replaceAll("<RETURN>", RETURN).
// replace("<RESULT_ERASED>", RESULT_ERASED).
// replace("<RETURN_ERASED>", RETURN_ERASED).
// replaceAll("<LINK_TO_STATIC_ARGS>", LINK_TO_STATIC_ARGS.stream().
// collect(joining(", "))).
// replace("<LINK_TO_INVOKER_ARGS>", LINK_TO_INVOKER_ARGS.stream().
// collect(joining(", ")))
// replaceAll("<LINK_TO_STATIC_ARGS>", String.join(", ", LINK_TO_STATIC_ARGS)).
// replace("<LINK_TO_INVOKER_ARGS>", String.join(", ", LINK_TO_INVOKER_ARGS))
// .indent(4);
// }
//
@ -916,30 +901,7 @@ final class VarHandles {
// }
//
// static char getCharType(Class<?> pt) {
// if (pt == void.class) {
// return 'V';
// }
// else if (!pt.isPrimitive()) {
// return 'L';
// }
// else if (pt == boolean.class) {
// return 'Z';
// }
// else if (pt == int.class) {
// return 'I';
// }
// else if (pt == long.class) {
// return 'J';
// }
// else if (pt == float.class) {
// return 'F';
// }
// else if (pt == double.class) {
// return 'D';
// }
// else {
// throw new IllegalStateException(pt.getName());
// }
// return Wrapper.forBasicType(pt).basicTypeChar();
// }
// }
}

View File

@ -440,28 +440,28 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ get(VarHandle ob) {
FieldStaticReadOnly handle = (FieldStaticReadOnly)ob;
FieldStaticReadOnly handle = (FieldStaticReadOnly) ob.target();
return UNSAFE.get$Type$(handle.base,
handle.fieldOffset);
}
@ForceInline
static $type$ getVolatile(VarHandle ob) {
FieldStaticReadOnly handle = (FieldStaticReadOnly)ob;
FieldStaticReadOnly handle = (FieldStaticReadOnly) ob.target();
return UNSAFE.get$Type$Volatile(handle.base,
handle.fieldOffset);
}
@ForceInline
static $type$ getOpaque(VarHandle ob) {
FieldStaticReadOnly handle = (FieldStaticReadOnly)ob;
FieldStaticReadOnly handle = (FieldStaticReadOnly) ob.target();
return UNSAFE.get$Type$Opaque(handle.base,
handle.fieldOffset);
}
@ForceInline
static $type$ getAcquire(VarHandle ob) {
FieldStaticReadOnly handle = (FieldStaticReadOnly)ob;
FieldStaticReadOnly handle = (FieldStaticReadOnly) ob.target();
return UNSAFE.get$Type$Acquire(handle.base,
handle.fieldOffset);
}
@ -496,7 +496,7 @@ final class VarHandle$Type$s {
@ForceInline
static void set(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
UNSAFE.put$Type$(handle.base,
handle.fieldOffset,
{#if[Object]?handle.fieldType.cast(value):value});
@ -504,7 +504,7 @@ final class VarHandle$Type$s {
@ForceInline
static void setVolatile(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
UNSAFE.put$Type$Volatile(handle.base,
handle.fieldOffset,
{#if[Object]?handle.fieldType.cast(value):value});
@ -512,7 +512,7 @@ final class VarHandle$Type$s {
@ForceInline
static void setOpaque(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
UNSAFE.put$Type$Opaque(handle.base,
handle.fieldOffset,
{#if[Object]?handle.fieldType.cast(value):value});
@ -520,7 +520,7 @@ final class VarHandle$Type$s {
@ForceInline
static void setRelease(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
UNSAFE.put$Type$Release(handle.base,
handle.fieldOffset,
{#if[Object]?handle.fieldType.cast(value):value});
@ -529,7 +529,7 @@ final class VarHandle$Type$s {
@ForceInline
static boolean compareAndSet(VarHandle ob, $type$ expected, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.compareAndSet$Type$(handle.base,
handle.fieldOffset,
{#if[Object]?handle.fieldType.cast(expected):expected},
@ -539,7 +539,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ compareAndExchange(VarHandle ob, $type$ expected, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.compareAndExchange$Type$(handle.base,
handle.fieldOffset,
{#if[Object]?handle.fieldType.cast(expected):expected},
@ -548,7 +548,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ compareAndExchangeAcquire(VarHandle ob, $type$ expected, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.compareAndExchange$Type$Acquire(handle.base,
handle.fieldOffset,
{#if[Object]?handle.fieldType.cast(expected):expected},
@ -557,7 +557,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ compareAndExchangeRelease(VarHandle ob, $type$ expected, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.compareAndExchange$Type$Release(handle.base,
handle.fieldOffset,
{#if[Object]?handle.fieldType.cast(expected):expected},
@ -566,7 +566,7 @@ final class VarHandle$Type$s {
@ForceInline
static boolean weakCompareAndSetPlain(VarHandle ob, $type$ expected, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.weakCompareAndSet$Type$Plain(handle.base,
handle.fieldOffset,
{#if[Object]?handle.fieldType.cast(expected):expected},
@ -575,7 +575,7 @@ final class VarHandle$Type$s {
@ForceInline
static boolean weakCompareAndSet(VarHandle ob, $type$ expected, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.weakCompareAndSet$Type$(handle.base,
handle.fieldOffset,
{#if[Object]?handle.fieldType.cast(expected):expected},
@ -584,7 +584,7 @@ final class VarHandle$Type$s {
@ForceInline
static boolean weakCompareAndSetAcquire(VarHandle ob, $type$ expected, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.weakCompareAndSet$Type$Acquire(handle.base,
handle.fieldOffset,
{#if[Object]?handle.fieldType.cast(expected):expected},
@ -593,7 +593,7 @@ final class VarHandle$Type$s {
@ForceInline
static boolean weakCompareAndSetRelease(VarHandle ob, $type$ expected, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.weakCompareAndSet$Type$Release(handle.base,
handle.fieldOffset,
{#if[Object]?handle.fieldType.cast(expected):expected},
@ -602,7 +602,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ getAndSet(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.getAndSet$Type$(handle.base,
handle.fieldOffset,
{#if[Object]?handle.fieldType.cast(value):value});
@ -610,7 +610,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ getAndSetAcquire(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.getAndSet$Type$Acquire(handle.base,
handle.fieldOffset,
{#if[Object]?handle.fieldType.cast(value):value});
@ -618,7 +618,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ getAndSetRelease(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.getAndSet$Type$Release(handle.base,
handle.fieldOffset,
{#if[Object]?handle.fieldType.cast(value):value});
@ -628,7 +628,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ getAndAdd(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.getAndAdd$Type$(handle.base,
handle.fieldOffset,
value);
@ -636,7 +636,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ getAndAddAcquire(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.getAndAdd$Type$Acquire(handle.base,
handle.fieldOffset,
value);
@ -644,7 +644,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ getAndAddRelease(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.getAndAdd$Type$Release(handle.base,
handle.fieldOffset,
value);
@ -654,7 +654,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ getAndBitwiseOr(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.getAndBitwiseOr$Type$(handle.base,
handle.fieldOffset,
value);
@ -662,7 +662,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ getAndBitwiseOrRelease(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.getAndBitwiseOr$Type$Release(handle.base,
handle.fieldOffset,
value);
@ -670,7 +670,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ getAndBitwiseOrAcquire(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.getAndBitwiseOr$Type$Acquire(handle.base,
handle.fieldOffset,
value);
@ -678,7 +678,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ getAndBitwiseAnd(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.getAndBitwiseAnd$Type$(handle.base,
handle.fieldOffset,
value);
@ -686,7 +686,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ getAndBitwiseAndRelease(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.getAndBitwiseAnd$Type$Release(handle.base,
handle.fieldOffset,
value);
@ -694,7 +694,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ getAndBitwiseAndAcquire(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.getAndBitwiseAnd$Type$Acquire(handle.base,
handle.fieldOffset,
value);
@ -702,7 +702,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ getAndBitwiseXor(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.getAndBitwiseXor$Type$(handle.base,
handle.fieldOffset,
value);
@ -710,7 +710,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ getAndBitwiseXorRelease(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.getAndBitwiseXor$Type$Release(handle.base,
handle.fieldOffset,
value);
@ -718,7 +718,7 @@ final class VarHandle$Type$s {
@ForceInline
static $type$ getAndBitwiseXorAcquire(VarHandle ob, $type$ value) {
FieldStaticReadWrite handle = (FieldStaticReadWrite)ob;
FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target();
return UNSAFE.getAndBitwiseXor$Type$Acquire(handle.base,
handle.fieldOffset,
value);

View File

@ -0,0 +1,217 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8291065
* @summary Checks interaction of static field VarHandle with class
* initialization mechanism..
* @run junit LazyInitializingTest
* @run junit/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=true LazyInitializingTest
* @run junit/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=false LazyInitializingTest
* @run junit/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=true
* -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=false LazyInitializingTest
*/
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.lang.constant.ConstantDescs;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.*;
public class LazyInitializingTest {
record SampleData(Runnable callback, int initialValue) {
private static final Runnable FAIL_ON_CLINIT_CALLBACK = () -> {
throw new AssertionError("Class shouldn't be initialized");
};
static final SampleData FAIL_ON_CLINIT = new SampleData();
SampleData() {
this(FAIL_ON_CLINIT_CALLBACK, 0);
}
}
record ClassInfo(MethodHandles.Lookup definingLookup, VarHandle vh) {}
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
/**
* Meta test to ensure the testing mechanism to check initialization is correct.
*/
@Test
public void testMeta() throws IllegalAccessException {
boolean[] val = new boolean[1];
var v0 = createSampleClass(new SampleData(() -> val[0] = true, 0));
assertFalse(val[0], "callback run before class init");
v0.definingLookup.ensureInitialized(v0.definingLookup.lookupClass());
assertTrue(val[0], "callback not run at class init");
}
@Test
public void testUninitializedOperations() {
var ci = createSampleClass(SampleData.FAIL_ON_CLINIT);
var vh = ci.vh;
vh.describeConstable();
vh.isAccessModeSupported(VarHandle.AccessMode.GET_AND_BITWISE_XOR_ACQUIRE);
vh.withInvokeExactBehavior();
vh.withInvokeBehavior();
vh.toMethodHandle(VarHandle.AccessMode.GET_AND_BITWISE_XOR_ACQUIRE);
vh.hasInvokeExactBehavior();
vh.accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_XOR_ACQUIRE);
}
@Test
public void testInitializationOnVarHandleUse() {
var initialized = new boolean[1];
var ci = createSampleClass(new SampleData(() -> initialized[0] = true, 42));
var vh = ci.vh;
assertEquals(42, (int) vh.get(), "VH does not read value set in class initializer");
assertTrue(initialized[0], "class initialization not captured");
}
@Test
public void testInitializationOnToMethodHandleUse() throws Throwable {
var initialized = new boolean[1];
var ci = createSampleClass(new SampleData(() -> initialized[0] = true, 42));
var mh = ci.vh.toMethodHandle(VarHandle.AccessMode.GET);
assertEquals(42, (int) mh.invokeExact(), "VH does not read value set in class initializer");
assertTrue(initialized[0], "class initialization not captured");
}
@Test
public void testParentChildLoading() throws Throwable {
// ChildSample: ensure only ParentSample (field declarer) is initialized
var l = new ParentChildLoader();
var childSampleClass = l.childClass();
var lookup = MethodHandles.privateLookupIn(childSampleClass, LOOKUP);
var childVh = lookup.findStaticVarHandle(childSampleClass, "f", int.class);
assertEquals(3, (int) childVh.get(), "Child class initialized unnecessarily");
lookup.ensureInitialized(childSampleClass);
assertEquals(6, (int) childVh.get(), "Child class was not initialized");
}
static ClassInfo createSampleClass(SampleData sampleData) {
try {
var lookup = LOOKUP.defineHiddenClassWithClassData(sampleClassBytes(), sampleData, false);
var vh = lookup.findStaticVarHandle(lookup.lookupClass(), "f", int.class);
return new ClassInfo(lookup, vh);
} catch (IllegalAccessException | NoSuchFieldException ex) {
throw new AssertionError(ex);
}
}
private static byte[] sampleClassBytes;
private static byte[] sampleClassBytes() {
var bytes = sampleClassBytes;
if (bytes != null)
return bytes;
try (var in = LazyInitializingTest.class.getResourceAsStream("LazyInitializingSample.class")) {
if (in == null)
throw new AssertionError("class file not found");
return sampleClassBytes = in.readAllBytes();
} catch (IOException ex) {
throw new AssertionError(ex);
}
}
}
// This is used as a template class, whose bytes are used to define
// hidden classes instead
class LazyInitializingSample {
static int f;
static {
try {
var data = MethodHandles.classData(MethodHandles.lookup(), ConstantDescs.DEFAULT_NAME,
LazyInitializingTest.SampleData.class);
Objects.requireNonNull(data);
data.callback().run();
f = data.initialValue();
} catch (IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
}
class ParentChildLoader extends ClassLoader {
ParentChildLoader() {
super(LazyInitializingTest.class.getClassLoader().getParent());
}
Class<?> parentClass() {
try {
return loadClass("ParentSample");
} catch (ClassNotFoundException e) {
throw new AssertionError(e);
}
}
Class<?> childClass() {
try {
return loadClass("ChildSample");
} catch (ClassNotFoundException e) {
throw new AssertionError(e);
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try (var stream = switch (name) {
case "ParentSample", "ChildSample" -> LazyInitializingTest.class.getResourceAsStream(name + ".class");
default -> throw new ClassNotFoundException(name);
}) {
if (stream == null)
throw new AssertionError();
var b = stream.readAllBytes();
return defineClass(name, b, 0, b.length);
} catch (IOException ex) {
throw new AssertionError(ex);
}
}
}
class ParentSample {
static int f;
static {
f = 3;
}
}
class ChildSample extends ParentSample {
static {
f = 6;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -145,7 +145,9 @@ abstract class VarHandleBaseTest {
}
message = message == null ? "" : message + ". ";
assertNotNull(_e, String.format("%sNo throwable thrown. Expected %s", message, re));
assertTrue(re.isInstance(_e), String.format("%sIncorrect throwable thrown, %s. Expected %s", message, _e, re));
if (!re.isInstance(_e)) {
fail(String.format("%sIncorrect throwable thrown, %s. Expected %s", message, _e, re), _e);
}
}

View File

@ -0,0 +1,160 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.java.lang.invoke;
import jdk.internal.classfile.Classfile;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.infra.Blackhole;
import java.lang.constant.ClassDesc;
import java.lang.constant.MethodTypeDesc;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import static java.lang.constant.ConstantDescs.*;
import static jdk.internal.classfile.Classfile.ACC_STATIC;
/**
* A benchmark ensuring that var and method handle lazy initialization are not
* too slow compared to eager initialization.
*/
@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
@Fork(value = 10, warmups = 5, jvmArgsAppend = {
"--add-exports", "java.base/jdk.internal.classfile=ALL-UNNAMED",
"--add-exports", "java.base/jdk.internal.classfile.attribute=ALL-UNNAMED",
"--add-exports", "java.base/jdk.internal.classfile.constantpool=ALL-UNNAMED",
"--add-exports", "java.base/jdk.internal.classfile.instruction=ALL-UNNAMED",
"--add-exports", "java.base/jdk.internal.classfile.components=ALL-UNNAMED"
})
public class LazyStaticColdStart {
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private Class<?> targetClass;
/**
* Ensures non-initialized targetClass is used and initializes the lazy/non-lazy handles
* to prevent further creation costs. (see static initializer block comments)
*/
@Setup(Level.Iteration)
public void setup() throws Throwable {
class Holder {
static final ClassDesc describedClass = LazyStaticColdStart.class.describeConstable().orElseThrow().nested("Data");
static final ClassDesc CD_ThreadLocalRandom = ThreadLocalRandom.class.describeConstable().orElseThrow();
static final ClassDesc CD_Blackhole = Blackhole.class.describeConstable().orElseThrow();
static final MethodTypeDesc MTD_void_long = MethodTypeDesc.of(CD_void, CD_long);
static final MethodTypeDesc MTD_ThreadLocalRandom = MethodTypeDesc.of(CD_ThreadLocalRandom);
static final MethodTypeDesc MTD_long = MethodTypeDesc.of(CD_long);
static final byte[] classBytes = Classfile.of().build(describedClass, clb -> {
clb.withField("v", CD_long, ACC_STATIC);
clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> {
cob.constantInstruction(100L);
cob.invokestatic(CD_Blackhole, "consumeCPU", MTD_void_long);
cob.invokestatic(CD_ThreadLocalRandom, "current", MTD_ThreadLocalRandom);
cob.invokevirtual(CD_ThreadLocalRandom, "nextLong", MTD_long);
cob.putstatic(describedClass, "v", CD_long);
cob.return_();
});
});
/*
* This static initializer eliminates overheads with initializing VarHandle/
* MethodHandle infrastructure that's not done in startup, so
* we are only measuring new Class initialization costs.
*/
static {
class AnotherLazy {
static long f;
}
try {
LOOKUP.findStaticVarHandle(AnotherLazy.class, "f", long.class); // lazy static VH
LOOKUP.findStaticGetter(AnotherLazy.class, "f", long.class); // lazy static MH
AnotherLazy.f = 5L; // initialize class
LOOKUP.findStaticVarHandle(AnotherLazy.class, "f", long.class); // static VH
LOOKUP.findStaticGetter(AnotherLazy.class, "f", long.class); // static MH
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
}
targetClass = LOOKUP.defineHiddenClass(Holder.classBytes, false).lookupClass();
}
@Benchmark
public VarHandle varHandleCreateLazy() throws Throwable {
return LOOKUP.findStaticVarHandle(targetClass, "v", long.class);
}
@Benchmark
public VarHandle varHandleCreateEager() throws Throwable {
LOOKUP.ensureInitialized(targetClass);
return LOOKUP.findStaticVarHandle(targetClass, "v", long.class);
}
@Benchmark
public long varHandleInitializeCallLazy() throws Throwable {
return (long) LOOKUP.findStaticVarHandle(targetClass, "v", long.class).get();
}
@Benchmark
public long varHandleInitializeCallEager() throws Throwable {
LOOKUP.ensureInitialized(targetClass);
return (long) LOOKUP.findStaticVarHandle(targetClass, "v", long.class).get();
}
@Benchmark
public MethodHandle methodHandleCreateLazy() throws Throwable {
return LOOKUP.findStaticGetter(targetClass, "v", long.class);
}
@Benchmark
public MethodHandle methodHandleCreateEager() throws Throwable {
LOOKUP.ensureInitialized(targetClass);
return LOOKUP.findStaticGetter(targetClass, "v", long.class);
}
@Benchmark
public long methodHandleInitializeCallLazy() throws Throwable {
return (long) LOOKUP.findStaticGetter(targetClass, "v", long.class).invokeExact();
}
@Benchmark
public long methodHandleInitializeCallEager() throws Throwable {
LOOKUP.ensureInitialized(targetClass);
return (long) LOOKUP.findStaticGetter(targetClass, "v", long.class).invokeExact();
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.java.lang.invoke;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
/**
* A benchmark testing lazy static var handle vs regular static var handle,
* to ensure the lazy static var handle doesn't have too much post-initialization
* invocation penalties.
*/
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@State(Scope.Thread)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(3)
public class VarHandleLazyStaticInvocation {
static final VarHandle initialized;
static final VarHandle lazy;
static long longField;
static {
try {
lazy = MethodHandles.lookup().findStaticVarHandle(Data.class, "longField", long.class);
initialized = MethodHandles.lookup().findStaticVarHandle(VarHandleLazyStaticInvocation.class, "longField", long.class);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
static class Data {
static long longField;
}
@Benchmark
public long lazyInvocation() {
lazy.set((long) ThreadLocalRandom.current().nextLong());
return (long) lazy.get();
}
@Benchmark
public long initializedInvocation() {
initialized.set((long) ThreadLocalRandom.current().nextLong());
return (long) initialized.get();
}
}