diff --git a/make/test/BuildMicrobenchmark.gmk b/make/test/BuildMicrobenchmark.gmk
index 347ca44d25f39..8b20327a16455 100644
--- a/make/test/BuildMicrobenchmark.gmk
+++ b/make/test/BuildMicrobenchmark.gmk
@@ -92,6 +92,7 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \
--add-exports java.base/jdk.internal.classfile.impl=ALL-UNNAMED \
--add-exports java.base/jdk.internal.event=ALL-UNNAMED \
--add-exports java.base/jdk.internal.foreign=ALL-UNNAMED \
+ --add-exports java.base/jdk.internal.lang.stable=ALL-UNNAMED \
--add-exports java.base/jdk.internal.misc=ALL-UNNAMED \
--add-exports java.base/jdk.internal.util=ALL-UNNAMED \
--add-exports java.base/jdk.internal.vm=ALL-UNNAMED \
diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java
new file mode 100644
index 0000000000000..d6bd5a06b5169
--- /dev/null
+++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdater.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (c) 2025, 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 jdk.internal.lang.stable;
+
+import jdk.internal.invoke.MhUtil;
+import jdk.internal.util.Architecture;
+import jdk.internal.vm.annotation.ForceInline;
+
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.VarHandle;
+import java.util.List;
+import java.util.Objects;
+
+import static jdk.internal.lang.stable.StableFieldUpdaterGenerator.*;
+
+/**
+ * Stable field updaters.
+ *
+ * This class allows, for example, effortless conversion of immutable classes to use lazy
+ * {@link Object#hashCode()}.
+ *
+ * Here is an example of how to convert
+ *
+ * {@snippet lang = java:
+ * public final class Foo {
+ *
+ * private final Bar bar;
+ * private final Baz baz;
+ *
+ * public Foo(Bar bar, Baz baz) {
+ * this.bar = bar;
+ * this.baz = baz;
+ * }
+ *
+ * @Override
+ * public boolean equals(Object o) {
+ * return o instanceof Foo that
+ * && Objects.equals(this.bar, that.bar)
+ * && Objects.equals(this.baz, that.baz);
+ * }
+ *
+ * @Override
+ * public int hashCode() {
+ * return Objects.hash(bar, baz);
+ * }
+ * }
+ *} to use {@code @Stable} lazy hashing:
+ *
+ * {@snippet lang = java:
+ * public final class LazyFoo {
+ *
+ * private final Bar bar;
+ * private final Baz baz;
+ *
+ * private static final MethodHandle HASH_UPDATER;
+ *
+ * static {
+ * MethodHandles.Lookup lookup = MethodHandles.lookup();
+ * try {
+ * VarHandle accessor = lookup.findVarHandle(LazyFoo.class, "hash", int.class);
+ * MethodHandle underlying = lookup.findStatic(LazyFoo.class, "hashCodeFor", MethodType.methodType(int.class, LazyFoo.class));
+ * HASH_UPDATER = StableFieldUpdater.atMostOnce(accessor, underlying);
+ * } catch (ReflectiveOperationException e) {
+ * throw new InternalError(e);
+ * }
+ * }
+ *
+ * @Stable
+ * private int hash;
+ *
+ * public LazyFoo(Bar bar, Baz baz) {
+ * this.bar = bar;
+ * this.baz = baz;
+ * }
+ *
+ * @Override
+ * public boolean equals(Object o) {
+ * return o instanceof Foo that
+ * && Objects.equals(this.bar, that.bar)
+ * && Objects.equals(this.baz, that.baz);
+ * }
+ *
+ * @Override
+ * public int hashCode() {
+ * try {
+ * return (int) HASH_UPDATER.invokeExact(this);
+ * } catch (Throwable e) {
+ * throw new RuntimeException(e);
+ * }
+ * }
+ *
+ * private static int hashCodeFor(LazyFoo foo) {
+ * return Objects.hash(foo.bar, foo.baz);
+ * }
+ * }
+ *}
+ *
+ * If the underlying hash lamba returns zero, the hash code will be {@code 0}. In such
+ * cases, {@link @Stable} fields cannot be constant-folded.
+ *
+ * In cases where the entire range of hash codes are strictly specified (as it is for
+ * {@code String}), a {@code long} field can be used instead, and a value of
+ * {@code 1 << 32} can be used as a token for zero (as the lower 32 bits are zero) and
+ * then just cast to an {@code int} as shown in this example:
+ *
+ * {@snippet lang = java:
+ * public final class LazySpecifiedFoo {
+ *
+ * private final Bar bar;
+ * private final Baz baz;
+ *
+ * private static final MethodHandle HASH_UPDATER;
+ *
+ * static {
+ * MethodHandles.Lookup lookup = MethodHandles.lookup();
+ * try {
+ * VarHandle accessor = lookup.findVarHandle(LazySpecifiedFoo.class, "hash", long.class);
+ * MethodHandle underlying = lookup.findStatic(LazySpecifiedFoo.class, "hashCodeFor", MethodType.methodType(long.class, LazySpecifiedFoo.class));
+ *
+ * // Replaces zero with 2^32. Bits 32-63 will then be masked away by the
+ * // `hashCode()` method using an (int) cast.
+ * underlying = StableFieldUpdater.replaceLongZero(underlying, 1L << 32);
+ *
+ * HASH_UPDATER = StableFieldUpdater.atMostOnce(accessor, underlying);
+ * } catch (ReflectiveOperationException e) {
+ * throw new InternalError(e);
+ * }
+ * }
+ *
+ * @Stable
+ * private long hash;
+ *
+ * public LazySpecifiedFoo(Bar bar, Baz baz) {
+ * this.bar = bar;
+ * this.baz = baz;
+ * }
+ *
+ * @Override
+ * public boolean equals(Object o) {
+ * return o instanceof Foo that
+ * && Objects.equals(this.bar, that.bar)
+ * && Objects.equals(this.baz, that.baz);
+ * }
+ *
+ * @Override
+ * public int hashCode() {
+ * try {
+ * return (int) (long) HASH_UPDATER.invokeExact(this);
+ * } catch (Throwable e) {
+ * throw new RuntimeException(e);
+ * }
+ * }
+ *
+ * private static long hashCodeFor(LazySpecifiedFoo foo) {
+ * return Objects.hash(foo.bar, foo.baz);
+ * }
+ * }
+ *}
+ * The example above also features a static method {@code hashCodeFor()} that acts as
+ * the underlying hash function. This method can reside in another class.
+ *
+ *
+ * The provided {@code underlying} function must not recurse or an IllegalStateException
+ * will be thrown.
+ *
+ * If a reference value of {@code null} is used as a parameter in any of the methods
+ * in this class, a {@link NullPointerException} is thrown.
+ */
+public final class StableFieldUpdater {
+
+ private StableFieldUpdater() {}
+
+ private static final MethodHandles.Lookup LOCAL_LOOKUP = MethodHandles.lookup();
+
+ /**
+ * {@return a function that lazily sets the field accessible via the provided
+ * {@code accessor} by invoking the provided {@code underlying} function
+ * if the field has its default value (e.g. zero). Otherwise, the
+ * returned function returns the set field value}
+ *
+ *
+ * @param accessor used to access a field
+ * @param underlying function which is invoked if a field has its
+ * default value.
+ * @throws NullPointerException if any of the provided parameters are {@code null}
+ * @throws IllegalArgumentException if the accessor's {@linkplain VarHandle#varType()}
+ * is not equal to the return type of the
+ * underlying's {@linkplain MethodHandle#type()}
+ * @throws IllegalArgumentException if the provided {@code underlying} function does
+ * take exactly one parameter of a reference type.
+ */
+ public static MethodHandle atMostOnce(VarHandle accessor,
+ MethodHandle underlying) {
+ // Implicit null check
+ final var accessorVarType = accessor.varType();
+ final var accessorCoordinateTypes = accessor.coordinateTypes();
+ final var underlyingType = underlying.type();
+
+ if (accessorVarType != underlyingType.returnType()) {
+ throw new IllegalArgumentException("Return type mismatch: accessor: " + accessor + ", underlying: " + underlying);
+ }
+
+ if (!accessorCoordinateTypes.equals(underlyingType.parameterList())) {
+ throw new IllegalArgumentException("Parameter type mismatch: accessor: " + accessor + ", underlying: " + underlying);
+ }
+
+ if (!accessor.isAccessModeSupported(VarHandle.AccessMode.SET)) {
+ throw new IllegalArgumentException("The accessor is read only: " + accessor);
+ }
+
+ // Allow `invokeExact()` of the `apply(Object)` method
+ final MethodHandle adaptedUnderlying = underlyingType.parameterType(0).equals(Object.class)
+ || underlyingType.parameterType(0).isArray()
+ ? underlying
+ : underlying.asType(underlyingType.changeParameterType(0, Object.class));
+
+ final MethodHandle initialAccessor = accessor.toMethodHandle(initialAccessMode(accessorVarType));
+
+ return StableFieldUpdaterGenerator.handle(accessor, initialAccessor, underlying);
+ }
+
+ private static VarHandle.AccessMode initialAccessMode(Class> varType) {
+ if (varType.isPrimitive()) {
+ if (!Architecture.is64bit() && (varType.equals(long.class) || varType.equals(double.class))) {
+ // JLS (24) 17.4 states that 64-bit fields tear under plain memory semantics.
+ // Opaque semantics provides "Bitwise Atomicity" thus avoiding tearing.
+ return VarHandle.AccessMode.GET_OPAQUE;
+ } else {
+ // Reordering does not affect primitive values
+ // Todo: This does not establish a happens-before relation.
+ // Plain semantics suffice here as we are not dealing with a reference (for
+ // a reference, the internal state initialization can be reordered with
+ // other store ops).
+ return VarHandle.AccessMode.GET;
+ }
+ } else {
+ // Acquire semantics is needed for reference variables to prevent the internal
+ // state initialization can be reordered with other store ops.
+ return VarHandle.AccessMode.GET_ACQUIRE;
+ }
+ }
+
+ /**
+ * {@return a method handle that will replace any zero value returned by the provided
+ * {@code underlying} handle with the provided {@code zeroReplacement}}
+ * @param underlying function to filter return values from
+ * @param zeroReplacement to replace any zero values returned by the {@code underlying}
+ * method handle.
+ */
+ public static MethodHandle replaceIntZero(MethodHandle underlying, int zeroReplacement) {
+
+ final class Holder {
+ private static final MethodHandle RETURN_FILTER =
+ MhUtil.findStatic(LOCAL_LOOKUP, "replaceZero", MethodType.methodType(int.class, int.class, int.class));
+ }
+ check(underlying, int.class);
+ return MethodHandles.filterReturnValue(underlying,
+ MethodHandles.insertArguments(Holder.RETURN_FILTER, 1, zeroReplacement));
+ }
+
+ /**
+ * {@return a method handle that will replace any zero value returned by the provided
+ * {@code underlying} handle with the provided {@code zeroReplacement}}
+ * @param underlying function to filter return values from
+ * @param zeroReplacement to replace any zero values returned by the {@code underlying}
+ * method handle.
+ */
+ public static MethodHandle replaceLongZero(MethodHandle underlying, long zeroReplacement) {
+
+ final class Holder {
+ private static final MethodHandle RETURN_FILTER =
+ MhUtil.findStatic(LOCAL_LOOKUP, "replaceZero", MethodType.methodType(long.class, long.class, long.class));
+ }
+
+ check(underlying, long.class);
+ return MethodHandles.filterReturnValue(underlying,
+ MethodHandles.insertArguments(Holder.RETURN_FILTER, 1, zeroReplacement));
+ }
+
+ /**
+ * {@return a method handle that will replace any null value returned by the provided
+ * {@code underlying} handle with the provided {@code nullReplacement}}
+ * @param underlying function to filter return values from
+ * @param nullReplacement to replace any zero values returned by the {@code underlying}
+ * method handle.
+ */
+ public static MethodHandle replaceReferenceNull(MethodHandle underlying, Object nullReplacement) {
+
+ final class Holder {
+ private static final MethodHandle RETURN_FILTER =
+ MhUtil.findStatic(LOCAL_LOOKUP, "replaceNull", MethodType.methodType(Object.class, Object.class, Object.class));
+ }
+ check(underlying, Object.class);
+ return MethodHandles.filterReturnValue(underlying,
+ MethodHandles.insertArguments(Holder.RETURN_FILTER, 1, nullReplacement));
+ }
+
+ // Used reflectively
+ @ForceInline
+ private static int replaceZero(int value, int zeroReplacement) {
+ return value == 0 ? zeroReplacement : value;
+ }
+
+ // Used reflectively
+ @ForceInline
+ private static long replaceZero(long value, long zeroReplacement) {
+ return value == 0 ? zeroReplacement : value;
+ }
+
+ // Used reflectively
+ // Cannot reuse `Objects::requireNonNullElse` as it prohibits `null` replacements
+ @ForceInline
+ private static Object replaceNull(Object value, Object nullReplacement) {
+ return value == null ? nullReplacement : value;
+ }
+
+ public static CallSite lazyAtMostOnce(MethodHandles.Lookup lookup,
+ String unused,
+ VarHandle accessor,
+ MethodHandle underlying) {
+ Objects.requireNonNull(accessor);
+ Objects.requireNonNull(underlying);
+ var handle = MhUtil.findStatic(LOCAL_LOOKUP,
+ "atMostOnce", MethodType.methodType(MethodHandle.class, VarHandle.class, MethodHandle.class));
+ return new ConstantCallSite(MethodHandles.insertArguments(handle, 0, accessor, underlying));
+ }
+
+ // Static support functions
+
+ private static void check(MethodHandle underlying, Class> returnType) {
+ // Implicit null check
+ final var underlyingType = underlying.type();
+ if (underlyingType.returnType() != returnType || underlyingType.parameterCount() != 1) {
+ throw new IllegalArgumentException("Illegal underlying function: " + underlying);
+ }
+ }
+
+}
diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdaterGenerator.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdaterGenerator.java
new file mode 100644
index 0000000000000..5371a1f874c1a
--- /dev/null
+++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFieldUpdaterGenerator.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (c) 2025, 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 jdk.internal.lang.stable;
+
+import jdk.internal.invoke.MhUtil;
+import jdk.internal.vm.annotation.ForceInline;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.VarHandle;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Stable field updater code generator.
+ */
+final class StableFieldUpdaterGenerator {
+
+ private StableFieldUpdaterGenerator() {}
+
+ private static final MethodHandles.Lookup LOCAL_LOOKUP = MethodHandles.lookup();
+
+ record Shape(Class> varType, List> coordinateTypes){
+
+ static Shape of(VarHandle varHandle) {
+ final List> shapeCoordinateTypes = new ArrayList<>(varHandle.coordinateTypes().size());
+ for (Class> coordinate: varHandle.coordinateTypes()) {
+ shapeCoordinateTypes.add(toObjectIfReference(coordinate));
+ }
+ return new Shape(toObjectIfReference(varHandle.varType()), shapeCoordinateTypes);
+ }
+
+ static Class> toObjectIfReference(Class> type) {
+ // Todo: fix arrays of references
+ return type.isPrimitive() || type.isArray()
+ ? type
+ : Object.class;
+ }
+
+ }
+
+ // Used to hold hand-rolled and bespoke resolvers and shapeConstructors
+ record Maker(MethodHandle applyResolver, MethodHandle shapeConstructor) {
+ MethodHandle make(VarHandle accessor, MethodHandle initialAccessor, MethodHandle underlying) {
+ try {
+ return applyResolver
+ .bindTo(shapeConstructor
+ .invoke(accessor, initialAccessor, underlying));
+ } catch (Throwable t) {
+ throw new InternalError(t);
+ }
+ }
+ }
+
+ static MethodHandle handle(VarHandle accessor,
+ MethodHandle initialAccessor,
+ MethodHandle underlying) {
+
+ final var underlyingType = underlying.type();
+
+ // Allow `invokeExact()` of the `apply(Object)` method
+ final var adaptedUnderlying = underlyingType.parameterType(0).equals(Object.class)
+ || underlyingType.parameterType(0).isArray()
+ ? underlying
+ : underlying.asType(underlyingType.changeParameterType(0, Object.class));
+
+ final var shape = Shape.of(accessor);
+ final var maker = MAKERS.computeIfAbsent(shape,
+ s -> {
+ // System.out.println("shape = " + s);
+ return bespokeMaker(accessor, initialAccessor, underlying);
+ });
+ final var handle = maker.make(accessor, initialAccessor, adaptedUnderlying);
+
+ return accessor.coordinateTypes().size() == 1
+ ? handle.asType(MethodType.methodType(handle.type().returnType(), underlying.type().parameterType(0)))
+ : handle;
+
+ }
+
+ private static Maker bespokeMaker(VarHandle accessor,
+ MethodHandle initialAccessor,
+ MethodHandle underlying) {
+
+ // Todo: Implement this using bytecode generation
+ throw new UnsupportedOperationException("No support for accessor: " + accessor +
+ ", initialAccessor: " + initialAccessor + ", underlying: " + underlying);
+ }
+
+
+ // Hand-rolled classes
+
+ static final MethodHandle INT_ONCE_APPLY =
+ MhUtil.findVirtual(LOCAL_LOOKUP, IntOnce.class, "apply",
+ MethodType.methodType(int.class, Object.class));
+
+ record IntOnce(VarHandle accessor,
+ MethodHandle initialAccessor,
+ MethodHandle underlying) {
+
+ // Used reflectively
+ @ForceInline
+ public int apply(Object t) {
+ try {
+ final int v = (int) initialAccessor.invoke(t);
+ return v == 0 ? applySlowPath(t) : v;
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public int applySlowPath(Object t) {
+ preventReentry(this);
+ int v;
+ synchronized (this) {
+ v = (int) accessor.getAcquire(t);
+ if (v == 0) {
+ try {
+ v = (int) underlying.invokeExact(t);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ accessor.setRelease(t, v);
+ }
+ }
+ return v;
+ }
+ }
+
+ static final MethodHandle LONG_ONCE_APPLY =
+ MhUtil.findVirtual(LOCAL_LOOKUP, LongOnce.class, "apply",
+ MethodType.methodType(long.class, Object.class));
+
+ record LongOnce(VarHandle accessor,
+ MethodHandle initialAccessor,
+ MethodHandle underlying) {
+
+ // Used reflectively
+ @ForceInline
+ public long apply(Object t) {
+ try {
+ long v = (long) initialAccessor.invoke(t);
+ return v == 0 ? applySlowPath(t) : v;
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public long applySlowPath(Object t) {
+ preventReentry(this);
+ long v;
+ synchronized (this) {
+ v = (long) accessor.getAcquire(t);
+ if (v == 0) {
+ try {
+ v = (long) underlying.invokeExact(t);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ accessor.setRelease(t, v);
+ }
+ }
+ return v;
+ }
+
+ }
+
+ static final MethodHandle REFERENCE_ONCE_APPLY =
+ MhUtil.findVirtual(LOCAL_LOOKUP, ReferenceOnce.class, "apply",
+ MethodType.methodType(Object.class, Object.class));
+
+ record ReferenceOnce(VarHandle accessor,
+ MethodHandle initialAccessor,
+ MethodHandle underlying) {
+
+ // Used reflectively
+ @ForceInline
+ public Object apply(Object t) {
+ try {
+ final Object v = (Object) initialAccessor.invoke(t);
+ return v == null ? applySlowPath(t) : v;
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public Object applySlowPath(Object t) {
+ preventReentry(this);
+ Object v;
+ synchronized (this) {
+ v = (Object) accessor.getAcquire(t);
+ if (v == null) {
+ try {
+ v = (int) underlying.invokeExact(t);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ accessor.setRelease(t, v);
+ }
+ }
+ return v;
+ }
+ }
+
+ // Arrays
+
+ static final MethodHandle INT_ARRAY_ONCE_APPLY =
+ MhUtil.findVirtual(LOCAL_LOOKUP, IntArrayOnce.class, "apply",
+ MethodType.methodType(int.class, int[].class, int.class));
+
+ record IntArrayOnce(VarHandle accessor,
+ MethodHandle initialAccessor,
+ MethodHandle underlying) {
+
+ // Used reflectively
+ @ForceInline
+ public int apply(int[] array, int i) {
+ try {
+ final int v = (int) initialAccessor.invoke(array, i);
+ return v == 0 ? applySlowPath(array, i) : v;
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public int applySlowPath(int[] array, int i) {
+ preventReentry(this);
+ int v;
+ synchronized (this) {
+ v = (int) accessor.getAcquire(array, i);
+ if (v == 0) {
+ try {
+ v = (int) underlying.invokeExact(array, i);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ accessor.setRelease(array, i, v);
+ }
+ }
+ return v;
+ }
+ }
+
+ static final MethodHandle LONG_ARRAY_ONCE_APPLY =
+ MhUtil.findVirtual(LOCAL_LOOKUP, LongArrayOnce.class, "apply",
+ MethodType.methodType(long.class, long[].class, int.class));
+
+ record LongArrayOnce(VarHandle accessor,
+ MethodHandle initialAccessor,
+ MethodHandle underlying) {
+
+ // Used reflectively
+ @ForceInline
+ public long apply(long[] array, int i) {
+ try {
+ final long v = (long) initialAccessor.invoke(array, i);
+ return v == 0 ? applySlowPath(array, i) : v;
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public long applySlowPath(long[] array, int i) {
+ preventReentry(this);
+ long v;
+ synchronized (this) {
+ v = (long) accessor.getAcquire(array, i);
+ if (v == 0) {
+ try {
+ v = (long) underlying.invokeExact(array, i);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ accessor.setRelease(array, i, v);
+ }
+ }
+ return v;
+ }
+ }
+
+
+ // This method is not annotated with @ForceInline as it is always called
+ // in a slow path.
+ static void preventReentry(Object obj) {
+ if (Thread.holdsLock(obj)) {
+ throw new IllegalStateException("Recursive initialization of a stable value is illegal");
+ }
+ }
+
+ static Class> componentTypeDeep(Class> type) {
+ return type.isArray()
+ ? componentTypeDeep(type.componentType())
+ : type;
+ }
+
+ // Caching of Shapes
+
+ private static final Map MAKERS = new ConcurrentHashMap<>();
+
+ static {
+ // Add hand-rolled classes
+
+ // Scalars
+ MAKERS.put(new Shape(int.class, List.of(Object.class)), new Maker(INT_ONCE_APPLY, findMakerConstructor(IntOnce.class)));
+ MAKERS.put(new Shape(long.class, List.of(Object.class)), new Maker(LONG_ONCE_APPLY, findMakerConstructor(LongOnce.class)));
+ MAKERS.put(new Shape(Object.class, List.of(Object.class)), new Maker(REFERENCE_ONCE_APPLY, findMakerConstructor(ReferenceOnce.class)));
+ // Arrays
+ MAKERS.put(new Shape(int.class, List.of(int[].class, int.class)), new Maker(INT_ARRAY_ONCE_APPLY, findMakerConstructor(IntArrayOnce.class)));
+ MAKERS.put(new Shape(long.class, List.of(long[].class, int.class)), new Maker(LONG_ARRAY_ONCE_APPLY, findMakerConstructor(LongArrayOnce.class)));
+ }
+
+ private static MethodHandle findMakerConstructor(Class> type) {
+ try {
+ return LOCAL_LOOKUP.findConstructor(type,
+ MethodType.methodType(void.class, VarHandle.class, MethodHandle.class, MethodHandle.class));
+ } catch (ReflectiveOperationException e) {
+ throw new InternalError(e);
+ }
+ }
+
+}
diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java
new file mode 100644
index 0000000000000..b850321eaff68
--- /dev/null
+++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterExampleTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) 2025, 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
+ * @summary Basic tests for StableFieldUpdaterExampleTest examples in javadoc
+ * @modules java.base/jdk.internal.lang.stable
+ * @modules java.base/jdk.internal.invoke
+ * @run junit StableFieldUpdaterExampleTest
+ */
+
+import jdk.internal.invoke.MhUtil;
+import jdk.internal.lang.stable.StableFieldUpdater;
+import org.junit.jupiter.api.Test;
+
+import java.lang.Override;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.VarHandle;
+import java.util.Objects;
+import java.util.function.ToIntFunction;
+import java.util.function.ToLongFunction;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * This test is to make sure the example in the documentation of
+ * {@linkplain StableFieldUpdater} remains correct and compilable.
+ */
+final class StableFieldUpdaterExampleTest {
+
+ @interface Stable {} // No access to the real @Stable
+
+ public static final class Bar extends Base {
+ public Bar(int hash) {
+ super(hash);
+ }
+ }
+
+ public static final class Baz extends Base {
+ public Baz(int hash) {
+ super(hash);
+ }
+ }
+
+ public static abstract class Base {
+
+ final int hash;
+
+ public Base(int hash) {
+ this.hash = hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof Base that &&
+ this.getClass().equals(that.getClass()) &&
+ this.hash == that.hash;
+ }
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[hash=" + hash + ']';
+ }
+ }
+
+
+ static // Intentionally in unblessed order to allow the example to look nice
+
+ public final class Foo {
+
+ private final Bar bar;
+ private final Baz baz;
+
+ public Foo(Bar bar, Baz baz) {
+ this.bar = bar;
+ this.baz = baz;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof Foo that
+ && Objects.equals(this.bar, that.bar)
+ && Objects.equals(this.baz, that.baz);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(bar, baz);
+ }
+ }
+
+ static
+
+ public final class LazyFoo {
+
+ private final Bar bar;
+ private final Baz baz;
+
+ private static final MethodHandle HASH_UPDATER;
+
+ static {
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ try {
+ VarHandle accessor = lookup.findVarHandle(LazyFoo.class, "hash", int.class);
+ MethodHandle underlying = lookup.findStatic(LazyFoo.class, "hashCodeFor", MethodType.methodType(int.class, LazyFoo.class));
+ HASH_UPDATER = StableFieldUpdater.atMostOnce(accessor, underlying);
+ } catch (ReflectiveOperationException e) {
+ throw new InternalError(e);
+ }
+ }
+
+ @Stable
+ private int hash;
+
+ public LazyFoo(Bar bar, Baz baz) {
+ this.bar = bar;
+ this.baz = baz;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof Foo that
+ && Objects.equals(this.bar, that.bar)
+ && Objects.equals(this.baz, that.baz);
+ }
+
+ @Override
+ public int hashCode() {
+ try {
+ return (int) HASH_UPDATER.invokeExact(this);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static int hashCodeFor(LazyFoo foo) {
+ return Objects.hash(foo.bar, foo.baz);
+ }
+ }
+
+ static
+
+ public final class LazySpecifiedFoo {
+
+ private final Bar bar;
+ private final Baz baz;
+
+ private static final MethodHandle HASH_UPDATER;
+
+ static {
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ try {
+ VarHandle accessor = lookup.findVarHandle(LazySpecifiedFoo.class, "hash", long.class);
+ MethodHandle underlying = lookup.findStatic(LazySpecifiedFoo.class, "hashCodeFor", MethodType.methodType(long.class, LazySpecifiedFoo.class));
+
+ // Replaces zero with 2^32. Bits 32-63 will then be masked away by the
+ // `hashCode()` method using an (int) cast.
+ underlying = StableFieldUpdater.replaceLongZero(underlying, 1L << 32);
+
+ HASH_UPDATER = StableFieldUpdater.atMostOnce(accessor, underlying);
+ } catch (ReflectiveOperationException e) {
+ throw new InternalError(e);
+ }
+ }
+
+ @Stable
+ private long hash;
+
+ public LazySpecifiedFoo(Bar bar, Baz baz) {
+ this.bar = bar;
+ this.baz = baz;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof Foo that
+ && Objects.equals(this.bar, that.bar)
+ && Objects.equals(this.baz, that.baz);
+ }
+
+ @Override
+ public int hashCode() {
+ try {
+ return (int) (long) HASH_UPDATER.invokeExact(this);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static long hashCodeFor(LazySpecifiedFoo foo) {
+ return Objects.hash(foo.bar, foo.baz);
+ }
+ }
+
+
+ @Test
+ void lazy() {
+ var bar = new Bar(1);
+ var baz = new Baz(2);
+
+ var foo = new LazyFoo(bar, baz);
+
+ assertEquals(Objects.hash(1, 2), foo.hashCode());
+ }
+
+}
diff --git a/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java
new file mode 100644
index 0000000000000..67cb299caaa5f
--- /dev/null
+++ b/test/jdk/java/lang/StableValue/StableFieldUpdaterTest.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (c) 2025, 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
+ * @summary Basic tests for StableFieldUpdater implementations
+ * @modules java.base/jdk.internal.lang.stable
+ * @modules java.base/jdk.internal.invoke
+ * @run junit StableFieldUpdaterTest
+ */
+
+import jdk.internal.invoke.MhUtil;
+import jdk.internal.lang.stable.StableFieldUpdater;
+import org.junit.jupiter.api.Named;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemoryLayout;
+import java.lang.foreign.MemorySegment;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.VarHandle;
+import java.lang.invoke.WrongMethodTypeException;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.IntFunction;
+import java.util.function.ToIntFunction;
+import java.util.function.ToLongFunction;
+import java.util.stream.Stream;
+
+import static java.lang.foreign.ValueLayout.JAVA_INT;
+import static org.junit.jupiter.api.Assertions.*;
+
+final class StableFieldUpdaterTest {
+
+ private static final String STRING = "Abc";
+ private static final int SIZE = 8;
+
+
+ private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
+
+ private static final VarHandle ACCESSOR;
+ private static final MethodHandle UNDERLYING;
+ private static final MethodHandle DOUBLE_UNDERLYING;
+
+ static {
+ try {
+ ACCESSOR = LOOKUP.findVarHandle(Foo.class, "hash", int.class);
+ UNDERLYING = LOOKUP.findStatic(Foo.class, "hashCodeFor", MethodType.methodType(int.class, Foo.class));
+ DOUBLE_UNDERLYING = LOOKUP.findStatic(StableFieldUpdaterTest.class, "doubleFrom", MethodType.methodType(double.class, Foo.class));
+ } catch (ReflectiveOperationException e) {
+ throw new InternalError(e);
+ }
+ }
+
+ @Test
+ void invariants() {
+ assertThrows(NullPointerException.class, () -> StableFieldUpdater.atMostOnce(null, UNDERLYING));
+ assertThrows(NullPointerException.class, () -> StableFieldUpdater.atMostOnce(ACCESSOR, null));
+ var xi = assertThrows(IllegalArgumentException.class, () -> StableFieldUpdater.atMostOnce(ACCESSOR, DOUBLE_UNDERLYING));
+ assertEquals("Return type mismatch: accessor: VarHandle[varType=int, coord=[class StableFieldUpdaterTest$Foo]], underlying: MethodHandle(Foo)double", xi.getMessage());
+
+ assertThrows(NullPointerException.class, () -> StableFieldUpdater.replaceIntZero((MethodHandle) null, 1));
+ assertThrows(NullPointerException.class, () -> StableFieldUpdater.replaceLongZero((MethodHandle) null, 1L));
+ }
+
+ @Test
+ void multiCoordinateIntArray() throws Throwable {
+ var accessor = MethodHandles.arrayElementVarHandle(int[].class);
+ var underlying = LOOKUP.findStatic(StableFieldUpdaterTest.class, "multiCoordinateMethod",
+ MethodType.methodType(int.class, int[].class, int.class));
+ var atMostOnce = StableFieldUpdater.atMostOnce(accessor, underlying);
+ int index = 1;
+ int[] array = new int[SIZE];
+ int val = (int) atMostOnce.invokeExact(array, index);
+ assertEquals(index, val);
+ }
+
+ @Test
+ void multiCoordinateLongArray() throws Throwable {
+ var accessor = MethodHandles.arrayElementVarHandle(long[].class);
+ var underlying = LOOKUP.findStatic(StableFieldUpdaterTest.class, "multiCoordinateMethod",
+ MethodType.methodType(long.class, long[].class, int.class));
+ var atMostOnce = StableFieldUpdater.atMostOnce(accessor, underlying);
+ int index = 1;
+ long[] array = new long[SIZE];
+ long val = (long) atMostOnce.invokeExact(array, index);
+ assertEquals(index, val);
+ }
+
+ @Test
+ void multiCoordinateSegment() throws Throwable {
+ var layout = MemoryLayout.sequenceLayout(SIZE, JAVA_INT);
+ var accessor = layout.varHandle(MemoryLayout.PathElement.sequenceElement());
+ accessor = MethodHandles.insertCoordinates(accessor, 1, 0L); // zero offset
+ var underlying = LOOKUP.findStatic(StableFieldUpdaterTest.class, "multiCoordinateMethod",
+ MethodType.methodType(int.class, MemorySegment.class, long.class));
+ var atMostOnce = StableFieldUpdater.atMostOnce(accessor, underlying);
+ long index = 1L;
+ try (var arena = Arena.ofConfined()) {
+ var segment = arena.allocate(layout);
+ int val = (int) atMostOnce.invokeExact(segment, index);
+ assertEquals(index, val);
+ }
+ }
+
+ // Used reflectively: int[], int
+ static int multiCoordinateMethod(int[] array, int index) {
+ return index;
+ }
+
+ // Used reflectively:
+ static long multiCoordinateMethod(long[] array, int index) {
+ return index;
+ }
+
+ // Used reflectively
+ static int multiCoordinateMethod(MemorySegment segment, long index) {
+ return (int) index;
+ }
+
+ @Test
+ void returnedHandleTypes() {
+ MethodHandle handle = StableFieldUpdater.atMostOnce(ACCESSOR, UNDERLYING);
+ assertEquals(int.class, handle.type().returnType());
+ assertEquals(Foo.class, handle.type().parameterType(0));
+ assertEquals(1, handle.type().parameterCount());
+ }
+
+ @Test
+ void foo() throws Throwable {
+ MethodHandle handle = StableFieldUpdater.atMostOnce(ACCESSOR, UNDERLYING);
+ Foo foo = new Foo(STRING);
+ int hash = (int) handle.invokeExact(foo);
+ assertEquals(STRING.hashCode(), hash);
+ }
+
+ @ParameterizedTest
+ @MethodSource("fooConstructors")
+ void basic(Function ctor) {
+ final HasHashField foo = ctor.apply(STRING);
+ assertEquals(0L, foo.hash());
+ int actual = foo.hashCode();
+ assertEquals(STRING.hashCode(), actual);
+ assertEquals(actual, foo.hash());
+ }
+
+ @Test
+ void recordFoo() throws ReflectiveOperationException {
+ record RecordFoo(String string, int hash) {
+
+ private int hashCodeFor() {
+ return string.hashCode();
+ }
+
+ }
+ var accessor = LOOKUP.findVarHandle(RecordFoo.class, "hash", int.class);
+ var underlying = LOOKUP.findVirtual(RecordFoo.class, "hashCodeFor", MethodType.methodType(int.class));
+
+ accessor.isAccessModeSupported(VarHandle.AccessMode.SET);
+
+ // The field is `final`
+ var x = assertThrows(IllegalArgumentException.class, () -> StableFieldUpdater.atMostOnce(accessor, underlying));
+ assertEquals("The accessor is read only: VarHandle[varType=int, coord=[class StableFieldUpdaterTest$1RecordFoo]]", x.getMessage());
+ }
+
+ @Test
+ void wrongReceiver() {
+ var handle = StableFieldUpdater.atMostOnce(ACCESSOR, UNDERLYING);
+ var wrongType = new InheritingFoo(STRING);
+
+ assertThrows(WrongMethodTypeException.class, () -> {
+ int hash = (int) handle.invokeExact(wrongType);
+ });
+
+ assertThrows(ClassCastException.class, () -> {
+ int hash = (int) handle.invoke(wrongType);
+ });
+ }
+
+ @Test
+ void lazyAtMostOnce() throws Throwable {
+ var lookup = MethodHandles.lookup();
+ CallSite callSite = StableFieldUpdater.lazyAtMostOnce(lookup, "", ACCESSOR, UNDERLYING);
+
+ MethodHandle hasher = (MethodHandle) callSite.getTarget().invoke();
+
+ var foo = new Foo(STRING);
+ int hash = (int) hasher.invoke(foo);
+ assertEquals(STRING.hashCode(), hash);
+ }
+
+ @Test
+ void replaceIntZeroHandle() throws Throwable {
+ int zeroReplacement = -1;
+ MethodHandle underlying = MethodHandles.identity(int.class);
+ var mod = StableFieldUpdater.replaceIntZero(underlying, zeroReplacement);
+ assertEquals(1, (int) mod.invoke(1));
+ assertEquals(zeroReplacement, (int) mod.invoke(0));
+ }
+
+ @Test
+ void replaceLongZeroHandle() throws Throwable {
+ long zeroReplacement = -1;
+ MethodHandle underlying = MethodHandles.identity(long.class);
+ var mod = StableFieldUpdater.replaceLongZero(underlying, zeroReplacement);
+ assertEquals(1L, (long) mod.invoke(1L));
+ assertEquals(zeroReplacement, (long) mod.invoke(0L));
+ }
+
+ static final class Foo implements HasHashField {
+
+ private static final MethodHandle HASH_UPDATER;
+
+ static {
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ try {
+ VarHandle accessor = lookup.findVarHandle(Foo.class, "hash", int.class);
+ MethodHandle underlying = lookup.findStatic(Foo.class, "hashCodeFor", MethodType.methodType(int.class, Foo.class));
+ HASH_UPDATER = StableFieldUpdater.atMostOnce(accessor, underlying);
+ } catch (ReflectiveOperationException e) {
+ throw new InternalError(e);
+ }
+ }
+
+ private final String string;
+
+ int hash;
+ long dummy;
+
+ public Foo(String string) {
+ this.string = string;
+ }
+
+ @Override
+ public int hashCode() {
+ try {
+ return (int) HASH_UPDATER.invokeExact(this);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public long hash() {
+ return hash;
+ }
+
+ private static int hashCodeFor(Foo foo) {
+ return foo.string.hashCode();
+ }
+ }
+
+ static final class LongFoo implements HasHashField {
+
+ private static final MethodHandle HASH_UPDATER;
+
+ static {
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ try {
+ VarHandle accessor = lookup.findVarHandle(LongFoo.class, "hash", long.class);
+ MethodHandle underlying = lookup.findStatic(LongFoo.class, "hashCodeFor", MethodType.methodType(long.class, LongFoo.class));
+ HASH_UPDATER = StableFieldUpdater.atMostOnce(accessor, underlying);
+ } catch (ReflectiveOperationException e) {
+ throw new InternalError(e);
+ }
+ }
+
+ private final String string;
+
+ long hash;
+ long dummy;
+
+ public LongFoo(String string) {
+ this.string = string;
+ }
+
+ @Override
+ public int hashCode() {
+ try {
+ return (int) (long) HASH_UPDATER.invokeExact(this);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public long hash() {
+ return hash;
+ }
+
+ private static long hashCodeFor(LongFoo foo) {
+ return foo.string.hashCode();
+ }
+
+ }
+
+
+
+ static final class InheritingFoo extends AbstractFoo implements HasHashField {
+
+ private static final MethodHandle HASH_UPDATER;
+
+ static {
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ try {
+ VarHandle accessor = lookup.findVarHandle(InheritingFoo.class, "hash", int.class);
+ MethodHandle underlying = lookup.findStatic(InheritingFoo.class, "hashCodeFor", MethodType.methodType(int.class, InheritingFoo.class));
+ HASH_UPDATER = StableFieldUpdater.atMostOnce(accessor, underlying);
+ } catch (ReflectiveOperationException e) {
+ throw new InternalError(e);
+ }
+ }
+
+ public InheritingFoo(String string) {
+ super(string);
+ }
+
+ @Override
+ public int hashCode() {
+ try {
+ return (int) HASH_UPDATER.invokeExact(this);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static int hashCodeFor(InheritingFoo foo) {
+ return foo.string.hashCode();
+ }
+ }
+
+ static abstract class AbstractFoo implements HasHashField {
+ final String string;
+ int hash;
+
+ public AbstractFoo(String string) {
+ this.string = string;
+ }
+
+ @Override
+ public long hash() {
+ return hash;
+ }
+ }
+
+ interface HasHashField {
+ long hash();
+ }
+
+ // Illegal underlying function for int and long
+ private static double doubleFrom(Foo foo) {
+ return 1;
+ }
+
+ // Apparently, `hashCode()` is invoked if we create a stream of just `HasHashField`
+ // instances so we provide the associated constructors instead.
+ static Stream> fooConstructors() {
+ return Stream.of(
+ Foo::new,
+ LongFoo::new,
+ InheritingFoo::new
+ );
+ }
+
+}
diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableFieldUpdaterBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableFieldUpdaterBenchmark.java
new file mode 100644
index 0000000000000..b05684a89d5c0
--- /dev/null
+++ b/test/micro/org/openjdk/bench/java/lang/stable/StableFieldUpdaterBenchmark.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2025, 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.stable;
+
+import jdk.internal.lang.stable.StableFieldUpdater;
+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.Threads;
+import org.openjdk.jmh.annotations.Warmup;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.VarHandle;
+import java.net.URI;
+import java.sql.Ref;
+import java.util.concurrent.TimeUnit;
+import java.util.function.ToIntFunction;
+
+/**
+ * Benchmark measuring StableValue performance
+ */
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@State(Scope.Benchmark) // Share the same state instance (for contention)
+@Warmup(iterations = 5, time = 1)
+@Measurement(iterations = 5, time = 2)
+@Fork(value = 2, jvmArgs = {"--enable-preview", "--add-exports", "java.base/jdk.internal.lang.stable=ALL-UNNAMED"})
+@Threads(Threads.MAX) // Benchmark under contention
+public class StableFieldUpdaterBenchmark {
+
+ private static final String STRING = "https://some.site.com";
+
+ private static final Base BASE = new Base(STRING);
+ private static final Updater UPDATER = new Updater(STRING);
+ private static final URI U_R_I = URI.create(STRING);
+
+ private final Base base = new Base(STRING);
+ private final Updater updater = new Updater(STRING);
+ private static final URI uri = URI.create(STRING);
+
+ @Benchmark
+ public int baseStatic() {
+ return BASE.hashCode();
+ }
+
+ @Benchmark
+ public int base() {
+ return base.hashCode();
+ }
+
+ @Benchmark
+ public int updaterStatic() {
+ return UPDATER.hashCode();
+ }
+
+ @Benchmark
+ public int updater() {
+ return updater.hashCode();
+ }
+
+ @Benchmark
+ public int uriStatic() {
+ return U_R_I.hashCode();
+ }
+
+ @Benchmark
+ public int uri() {
+ return uri.hashCode();
+ }
+
+ static final class Base extends Abstract {
+ Base(String string) { super(string); }
+
+ @Override
+ public int hashCode() {
+ int h = hashCode;
+ if (h == 0) {
+ hashCode = h = string.hashCode();
+ }
+ return h;
+ }
+ }
+
+ static final class Updater extends Abstract {
+
+ private static final MethodHandle HASH_UPDATER;
+
+ static {
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ try {
+ var varHandle = lookup.findVarHandle(Updater.class, "hashCode", int.class);
+ var accessor = lookup.findVirtual(Updater.class, "hashCode0", MethodType.methodType(int.class));
+ HASH_UPDATER = StableFieldUpdater.atMostOnce(varHandle, accessor);
+ } catch (ReflectiveOperationException e) {
+ throw new InternalError(e);
+ }
+ }
+
+ Updater(String string) { super(string); }
+
+ @Override
+ public int hashCode() {
+ try {
+ return (int) HASH_UPDATER.invokeExact(this);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private int hashCode0() {
+ return string.hashCode();
+ }
+
+ }
+
+ static class Abstract {
+
+ final String string;
+ int hashCode;
+
+ Abstract(String string) {
+ this.string = string;
+ }
+ }
+
+}