From 201e3bcf52cac64510d50fbdfcd522638d1d0442 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Tue, 18 Jul 2023 00:58:25 +0000 Subject: [PATCH] 8291065: Creating a VarHandle for a static field triggers class initialization Reviewed-by: mchung, psandoz --- .../java/lang/invoke/IndirectVarHandle.java | 11 +- .../invoke/LazyInitializingVarHandle.java | 136 +++++++++++ .../java/lang/invoke/MethodHandles.java | 2 +- .../classes/java/lang/invoke/VarHandle.java | 50 +++- .../classes/java/lang/invoke/VarHandles.java | 178 ++++++-------- .../lang/invoke/X-VarHandle.java.template | 62 ++--- .../VarHandles/LazyInitializingTest.java | 217 ++++++++++++++++++ .../invoke/VarHandles/VarHandleBaseTest.java | 6 +- .../java/lang/invoke/LazyStaticColdStart.java | 160 +++++++++++++ .../invoke/VarHandleLazyStaticInvocation.java | 82 +++++++ 10 files changed, 744 insertions(+), 160 deletions(-) create mode 100644 src/java.base/share/classes/java/lang/invoke/LazyInitializingVarHandle.java create mode 100644 test/jdk/java/lang/invoke/VarHandles/LazyInitializingTest.java create mode 100644 test/micro/org/openjdk/bench/java/lang/invoke/LazyStaticColdStart.java create mode 100644 test/micro/org/openjdk/bench/java/lang/invoke/VarHandleLazyStaticInvocation.java diff --git a/src/java.base/share/classes/java/lang/invoke/IndirectVarHandle.java b/src/java.base/share/classes/java/lang/invoke/IndirectVarHandle.java index 3e804ec4f11..96d94bab409 100644 --- a/src/java.base/share/classes/java/lang/invoke/IndirectVarHandle.java +++ b/src/java.base/share/classes/java/lang/invoke/IndirectVarHandle.java @@ -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); - } } diff --git a/src/java.base/share/classes/java/lang/invoke/LazyInitializingVarHandle.java b/src/java.base/share/classes/java/lang/invoke/LazyInitializingVarHandle.java new file mode 100644 index 00000000000..ce992a2cd10 --- /dev/null +++ b/src/java.base/share/classes/java/lang/invoke/LazyInitializingVarHandle.java @@ -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 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); + } + } +} diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java index 7fc31c63b56..e0190ddab5a 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java @@ -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. */ diff --git a/src/java.base/share/classes/java/lang/invoke/VarHandle.java b/src/java.base/share/classes/java/lang/invoke/VarHandle.java index 2ffa75cf12b..79607068801 100644 --- a/src/java.base/share/classes/java/lang/invoke/VarHandle.java +++ b/src/java.base/share/classes/java/lang/invoke/VarHandle.java @@ -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"); }; } diff --git a/src/java.base/share/classes/java/lang/invoke/VarHandles.java b/src/java.base/share/classes/java/lang/invoke/VarHandles.java index 13993ca8f10..c0db5ddb866 100644 --- a/src/java.base/share/classes/java/lang/invoke/VarHandles.java +++ b/src/java.base/share/classes/java/lang/invoke/VarHandles.java @@ -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> 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> params = new LinkedHashMap<>(); +// var params = new java.util.LinkedHashMap>(); // 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). // replace("", NAME). @@ -851,12 +840,10 @@ final class VarHandles { // // Generate method // params.remove("ad"); // -// List LINK_TO_STATIC_ARGS = params.keySet().stream(). -// collect(toList()); +// List LINK_TO_STATIC_ARGS = new ArrayList<>(params.keySet()); // LINK_TO_STATIC_ARGS.add("handle.vform.getMemberName(ad.mode)"); // -// List LINK_TO_INVOKER_ARGS = params.keySet().stream(). -// collect(toList()); +// List 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). // replace("", RESULT_ERASED). // replace("", RETURN_ERASED). -// replaceAll("", LINK_TO_STATIC_ARGS.stream(). -// collect(joining(", "))). -// replace("", LINK_TO_INVOKER_ARGS.stream(). -// collect(joining(", "))) +// replaceAll("", String.join(", ", LINK_TO_STATIC_ARGS)). +// replace("", 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(); // } // } } diff --git a/src/java.base/share/classes/java/lang/invoke/X-VarHandle.java.template b/src/java.base/share/classes/java/lang/invoke/X-VarHandle.java.template index 36a646e6398..4bc9daf8433 100644 --- a/src/java.base/share/classes/java/lang/invoke/X-VarHandle.java.template +++ b/src/java.base/share/classes/java/lang/invoke/X-VarHandle.java.template @@ -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); diff --git a/test/jdk/java/lang/invoke/VarHandles/LazyInitializingTest.java b/test/jdk/java/lang/invoke/VarHandles/LazyInitializingTest.java new file mode 100644 index 00000000000..8b8831538b0 --- /dev/null +++ b/test/jdk/java/lang/invoke/VarHandles/LazyInitializingTest.java @@ -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; + } +} \ No newline at end of file diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleBaseTest.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleBaseTest.java index 81978c7a79c..0c9a7fd3953 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleBaseTest.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleBaseTest.java @@ -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); + } } diff --git a/test/micro/org/openjdk/bench/java/lang/invoke/LazyStaticColdStart.java b/test/micro/org/openjdk/bench/java/lang/invoke/LazyStaticColdStart.java new file mode 100644 index 00000000000..976554a259a --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/invoke/LazyStaticColdStart.java @@ -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(); + } +} diff --git a/test/micro/org/openjdk/bench/java/lang/invoke/VarHandleLazyStaticInvocation.java b/test/micro/org/openjdk/bench/java/lang/invoke/VarHandleLazyStaticInvocation.java new file mode 100644 index 00000000000..372caa55b00 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/invoke/VarHandleLazyStaticInvocation.java @@ -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(); + } +}