8291065: Creating a VarHandle for a static field triggers class initialization
Reviewed-by: mchung, psandoz
This commit is contained in:
parent
a53345ad03
commit
201e3bcf52
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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. */
|
||||
|
@ -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");
|
||||
};
|
||||
}
|
||||
|
@ -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();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
@ -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);
|
||||
|
217
test/jdk/java/lang/invoke/VarHandles/LazyInitializingTest.java
Normal file
217
test/jdk/java/lang/invoke/VarHandles/LazyInitializingTest.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user