From 4a23687bbb2d5a7a8ac5bbb00aabaac7d353a186 Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Thu, 25 Jun 2026 09:07:21 +0100 Subject: [PATCH 1/6] Initial commit --- src/java.base/share/classes/java/lang/Object.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/java.base/share/classes/java/lang/Object.java b/src/java.base/share/classes/java/lang/Object.java index 705c524eb75..632625396f0 100644 --- a/src/java.base/share/classes/java/lang/Object.java +++ b/src/java.base/share/classes/java/lang/Object.java @@ -186,15 +186,16 @@ public boolean equals(Object obj) { /** * Creates and returns a copy of this object. The precise meaning * of "copy" may depend on the class of the object. The general - * intent is that, for any object {@code x}, the expression: + * intent is that, for an object {@code x}, the expression: *
*
      * x.clone() != x
- * will be true, and that the expression: + * will be true if {@code x} is an {@linkplain java.util.Objects#hasIdentity(Object) + * identity object}, and that the expression: *
*
      * x.clone().getClass() == x.getClass()
- * will be {@code true}, but these are not absolute requirements. + * will be {@code true} for any object, but these are not absolute requirements. * While it is typically the case that: *
*
@@ -225,9 +226,11 @@ public boolean equals(Object obj) {
      * are considered to implement the interface {@code Cloneable} and that
      * the return type of the {@code clone} method of an array type {@code T[]}
      * is {@code T[]} where T is any reference or primitive type.
-     * Otherwise, this method creates a new instance of the class of this
-     * object and initializes all its fields with exactly the contents of
-     * the corresponding fields of this object, as if by assignment; the
+     * 

+ * If this object is a value object, this method simply returns the object. + * Otherwise, this object has identity and the method creates a new instance of + * the class of this object and initializes all its fields with exactly the + * contents of the corresponding fields of this object, as if by assignment; the * contents of the fields are not themselves cloned. Thus, this method * performs a "shallow copy" of this object, not a "deep copy" operation. *

From 7889c7d9bdc0e1cb2c9d8865a05fddaf986d2bb8 Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Fri, 26 Jun 2026 16:38:10 +0100 Subject: [PATCH 2/6] More improvements --- .../share/classes/java/lang/Object.java | 81 +++++++++++-------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/src/java.base/share/classes/java/lang/Object.java b/src/java.base/share/classes/java/lang/Object.java index 632625396f0..aaa291e2b9a 100644 --- a/src/java.base/share/classes/java/lang/Object.java +++ b/src/java.base/share/classes/java/lang/Object.java @@ -35,12 +35,22 @@ * *

*
- * When preview features are enabled, subclasses of {@code java.lang.Object} can be either - * identity classes or {@linkplain Class#isValue value classes}. + * When preview features are enabled, subclasses of {@code java.lang.Object} + * are either identity classes or {@linkplain Class#isValue value classes}. * See The Java Language Specification {@jls value-objects-8.1.1.5 Value Classes}. - * Use of value class instances for synchronization or with - * {@linkplain java.lang.ref.Reference object references} results in - * {@link IdentityException}. + * All classes are considered identity classes when preview features are disabled. + *

+ * Instances of value classes, known as value objects, do not have an + * associated synchronization monitor that a thread can lock or unlock. An + * attempt to {@code synchronize} on a value object causes {@link + * IdentityException} to be thrown. + *

+ * Value objects do not support finalization. The {@link #finalize()} method + * of a value object will never be invoked by the garbage collector. + *

+ * A {@linkplain java.lang.ref.Reference Reference Object} can only refer to an + * object with identity. Creating a reference object with a value object as + * the referent throws {@code IdentityException}. *

*
* @@ -313,14 +323,14 @@ public String toString() { * Only one thread at a time can own an object's monitor. *
*
- * If this object is a {@linkplain java.util.Objects#hasIdentity value object}, - * it does not have a monitor, an {@code IllegalMonitorStateException} is thrown. + * Value objects do not have an associated synchronization monitor that a + * thread can lock or unlock. An attempt to {@code synchronize} on a value + * object causes {@link IdentityException} to be thrown. *
*
* * @throws IllegalMonitorStateException if the current thread is not - * the owner of this object's monitor or - * if this object is a {@linkplain java.util.Objects#hasIdentity value object}. + * the owner of this object's monitor. * @see java.lang.Object#notifyAll() * @see java.lang.Object#wait() */ @@ -346,14 +356,14 @@ public String toString() { * *
*
- * If this object is a {@linkplain java.util.Objects#hasIdentity value object}, - * it does not have a monitor, an {@code IllegalMonitorStateException} is thrown. + * Value objects do not have an associated synchronization monitor that a + * thread can lock or unlock. An attempt to {@code synchronize} on a value + * object causes {@link IdentityException} to be thrown. *
*
* * @throws IllegalMonitorStateException if the current thread is not - * the owner of this object's monitor or - * if this object is a {@linkplain java.util.Objects#hasIdentity value object}. + * the owner of this object's monitor. * @see java.lang.Object#notify() * @see java.lang.Object#wait() */ @@ -370,14 +380,14 @@ public String toString() { * *
*
- * If this object is a {@linkplain java.util.Objects#hasIdentity value object}, - * it does not have a monitor, an {@code IllegalMonitorStateException} is thrown. + * Value objects do not have an associated synchronization monitor that a + * thread can lock or unlock. An attempt to {@code synchronize} on a value + * object causes {@link IdentityException} to be thrown. *
*
* * @throws IllegalMonitorStateException if the current thread is not - * the owner of the object's monitor or - * if this object is a {@linkplain java.util.Objects#hasIdentity value object}. + * the owner of the object's monitor * @throws InterruptedException if any thread interrupted the current thread before or * while the current thread was waiting. The interrupted status of the * current thread is cleared when this exception is thrown. @@ -401,16 +411,16 @@ public final void wait() throws InterruptedException { * *
*
- * If this object is a {@linkplain java.util.Objects#hasIdentity value object}, - * it does not have a monitor, an {@code IllegalMonitorStateException} is thrown. + * Value objects do not have an associated synchronization monitor that a + * thread can lock or unlock. An attempt to {@code synchronize} on a value + * object causes {@link IdentityException} to be thrown. *
*
* * @param timeoutMillis the maximum time to wait, in milliseconds * @throws IllegalArgumentException if {@code timeoutMillis} is negative * @throws IllegalMonitorStateException if the current thread is not - * the owner of the object's monitor or - * if this object is a {@linkplain java.util.Objects#hasIdentity value object}. + * the owner of the object's monitor * @throws InterruptedException if any thread interrupted the current thread before or * while the current thread was waiting. The interrupted status of the * current thread is cleared when this exception is thrown. @@ -505,8 +515,9 @@ public final void wait(long timeoutMillis) throws InterruptedException { * *
*
- * If this object is a {@linkplain java.util.Objects#hasIdentity value object}, - * it does not have a monitor, an {@code IllegalMonitorStateException} is thrown. + * Value objects do not have an associated synchronization monitor that a + * thread can lock or unlock. An attempt to {@code synchronize} on a value + * object causes {@link IdentityException} to be thrown. *
*
* @@ -532,8 +543,7 @@ public final void wait(long timeoutMillis) throws InterruptedException { * @throws IllegalArgumentException if {@code timeoutMillis} is negative, * or if the value of {@code nanos} is out of range * @throws IllegalMonitorStateException if the current thread is not - * the owner of the object's monitor or - * if this object is a {@linkplain java.util.Objects#hasIdentity value object}. + * the owner of the object's monitor * @throws InterruptedException if any thread interrupted the current thread before or * while the current thread was waiting. The interrupted status of the * current thread is cleared when this exception is thrown. @@ -560,14 +570,22 @@ public final void wait(long timeoutMillis, int nanos) throws InterruptedExceptio } /** - * Called by the garbage collector on an object when garbage collection + * Called by the garbage collector on an {@linkplain + * java.util.Objects#hasIdentity(Object) identity object} when garbage collection * determines that there are no more references to the object. * A subclass overrides the {@code finalize} method to dispose of * system resources or to perform other cleanup. + *
+ *
+ * Value classes do not support finalization. If this object is a value + * object, the {@code finalize} method will never be called by the garbage + * collector. + *
+ *
*

* When running in a Java virtual machine in which finalization has been - * disabled or removed, the garbage collector will never call - * {@code finalize()}. In a Java virtual machine in which finalization is + * disabled or removed, the garbage collector will never call {@code finalize()} + * for any object. In a Java virtual machine in which finalization is * enabled, the garbage collector might call {@code finalize} only after an * indefinite delay. *

@@ -610,13 +628,6 @@ public final void wait(long timeoutMillis, int nanos) throws InterruptedExceptio * the finalization of this object to be halted, but is otherwise * ignored. * - *

- *
- * If this object is a {@linkplain java.util.Objects#hasIdentity value object}, - * this method will never be invoked by the garbage collector. - *
- *
- * * @apiNote * Classes that embed non-heap resources have many options * for cleanup of those resources. The class must ensure that the From a56b3e36647f4a9ba719c61029437313523672c1 Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Sun, 28 Jun 2026 07:36:09 +0100 Subject: [PATCH 3/6] More improvements --- .../share/classes/java/lang/Object.java | 84 +++++++++++-------- 1 file changed, 49 insertions(+), 35 deletions(-) diff --git a/src/java.base/share/classes/java/lang/Object.java b/src/java.base/share/classes/java/lang/Object.java index aaa291e2b9a..23e2b38312d 100644 --- a/src/java.base/share/classes/java/lang/Object.java +++ b/src/java.base/share/classes/java/lang/Object.java @@ -37,16 +37,15 @@ *
* When preview features are enabled, subclasses of {@code java.lang.Object} * are either identity classes or {@linkplain Class#isValue value classes}. + * A value object is an instance of a non-abstract value class. * See The Java Language Specification {@jls value-objects-8.1.1.5 Value Classes}. * All classes are considered identity classes when preview features are disabled. *

- * Instances of value classes, known as value objects, do not have an - * associated synchronization monitor that a thread can lock or unlock. An - * attempt to {@code synchronize} on a value object causes {@link - * IdentityException} to be thrown. + * It is not possible to synchronize on a value object. An attempt to {@code + * synchronize} on a value object causes {@link IdentityException} to be thrown. *

- * Value objects do not support finalization. The {@link #finalize()} method - * of a value object will never be invoked by the garbage collector. + * The {@link #finalize()} method of a value object will never be invoked by + * the garbage collector. *

* A {@linkplain java.lang.ref.Reference Reference Object} can only refer to an * object with identity. Creating a reference object with a value object as @@ -195,38 +194,47 @@ public boolean equals(Object obj) { /** * Creates and returns a copy of this object. The precise meaning - * of "copy" may depend on the class of the object. The general - * intent is that, for an object {@code x}, the expression: + * of "copy" depends on the class of the object. + *

+ * For an {@linkplain java.util.Objects#hasIdentity(Object) identity object} + * {@code x}, the general intent is that the expression: *

*
      * x.clone() != x
- * will be true if {@code x} is an {@linkplain java.util.Objects#hasIdentity(Object) - * identity object}, and that the expression: + * will be true, and that the expression: *
*
      * x.clone().getClass() == x.getClass()
- * will be {@code true} for any object, but these are not absolute requirements. + * will be {@code true}, but these are not absolute requirements. + *

* While it is typically the case that: *

*
      * x.clone().equals(x)
* will be {@code true}, this is not an absolute requirement. *

+ * For a value object {@code x} the expectation that {@code x.clone() != x} is + * not meaningful. + *

* By convention, the returned object should be obtained by calling * {@code super.clone}. If a class and all of its superclasses (except * {@code Object}) obey this convention, it will be the case that * {@code x.clone().getClass() == x.getClass()}. *

- * By convention, the object returned by this method should be independent - * of this object (which is being cloned). To achieve this independence, - * it may be necessary to modify one or more fields of the object returned - * by {@code super.clone} before returning it. Typically, this means + * By convention, invoking this method on an identity object should return an + * object that is independent of this object (which is being cloned). To achieve + * this independence, it may be necessary to modify one or more fields of the object + * returned by {@code super.clone} before returning it. Typically, this means * copying any mutable objects that comprise the internal "deep structure" * of the object being cloned and replacing the references to these * objects with references to the copies. If a class contains only * primitive fields or references to immutable objects, then it is usually * the case that no fields in the object returned by {@code super.clone} * need to be modified. + *

+ * Value classes that store references to identity objects may wish to + * override the {@code clone} method and perform a "deep copy" of the + * identity objects. * * @implSpec * The method {@code clone} for class {@code Object} performs a @@ -237,13 +245,15 @@ public boolean equals(Object obj) { * the return type of the {@code clone} method of an array type {@code T[]} * is {@code T[]} where T is any reference or primitive type. *

- * If this object is a value object, this method simply returns the object. - * Otherwise, this object has identity and the method creates a new instance of + * For an identity object, this method creates a new instance of * the class of this object and initializes all its fields with exactly the * contents of the corresponding fields of this object, as if by assignment; the * contents of the fields are not themselves cloned. Thus, this method * performs a "shallow copy" of this object, not a "deep copy" operation. *

+ * For a value object, this method simply returns an object that is + * indistinguishable from the original ({@code x.clone() == x}). + *

* The class {@code Object} does not itself implement the interface * {@code Cloneable}, so calling the {@code clone} method on an object * whose class is {@code Object} will result in throwing an @@ -323,9 +333,10 @@ public String toString() { * Only one thread at a time can own an object's monitor. *

*
- * Value objects do not have an associated synchronization monitor that a - * thread can lock or unlock. An attempt to {@code synchronize} on a value - * object causes {@link IdentityException} to be thrown. + * The {@code notify} method requires that the current thread be the owner + * of the object's monitor. Since it is not possible to synchronize on a + * value object, an attempt to call this method on a value object will + * always fail with {@code IllegalMonitorStateException}. *
*
* @@ -356,9 +367,10 @@ public String toString() { * *
*
- * Value objects do not have an associated synchronization monitor that a - * thread can lock or unlock. An attempt to {@code synchronize} on a value - * object causes {@link IdentityException} to be thrown. + * The {@code notifyAll} method requires that the current thread be the owner + * of the object's monitor. Since it is not possible to synchronize on a + * value object, an attempt to call this method on a value object will + * always fail with {@code IllegalMonitorStateException}. *
*
* @@ -380,9 +392,10 @@ public String toString() { * *
*
- * Value objects do not have an associated synchronization monitor that a - * thread can lock or unlock. An attempt to {@code synchronize} on a value - * object causes {@link IdentityException} to be thrown. + * The {@code wait} method requires that the current thread be the owner + * of the object's monitor. Since it is not possible to synchronize on a + * value object, an attempt to call this method on a value object will + * always fail with {@code IllegalMonitorStateException}. *
*
* @@ -411,9 +424,10 @@ public final void wait() throws InterruptedException { * *
*
- * Value objects do not have an associated synchronization monitor that a - * thread can lock or unlock. An attempt to {@code synchronize} on a value - * object causes {@link IdentityException} to be thrown. + * The {@code wait} method requires that the current thread be the owner + * of the object's monitor. Since it is not possible to synchronize on a + * value object, an attempt to call this method on a value object will + * always fail with {@code IllegalMonitorStateException}. *
*
* @@ -515,9 +529,10 @@ public final void wait(long timeoutMillis) throws InterruptedException { * *
*
- * Value objects do not have an associated synchronization monitor that a - * thread can lock or unlock. An attempt to {@code synchronize} on a value - * object causes {@link IdentityException} to be thrown. + * The {@code wait} method requires that the current thread be the owner + * of the object's monitor. Since it is not possible to synchronize on a + * value object, an attempt to call this method on a value object will + * always fail with {@code IllegalMonitorStateException}. *
*
* @@ -577,9 +592,8 @@ public final void wait(long timeoutMillis, int nanos) throws InterruptedExceptio * system resources or to perform other cleanup. *
*
- * Value classes do not support finalization. If this object is a value - * object, the {@code finalize} method will never be called by the garbage - * collector. + * The {@link #finalize()} method of a value object will never be invoked + * by the garbage collector. *
*
*

From 064fb5470bcbe193112041cfd20d97587a46554b Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Mon, 29 Jun 2026 12:20:40 +0100 Subject: [PATCH 4/6] More word smithing --- .../share/classes/java/lang/Object.java | 63 +++++---- test/jdk/java/lang/Object/ValueObjects.java | 124 ++++++++++++++++++ 2 files changed, 158 insertions(+), 29 deletions(-) create mode 100644 test/jdk/java/lang/Object/ValueObjects.java diff --git a/src/java.base/share/classes/java/lang/Object.java b/src/java.base/share/classes/java/lang/Object.java index 23e2b38312d..4e76263d3ff 100644 --- a/src/java.base/share/classes/java/lang/Object.java +++ b/src/java.base/share/classes/java/lang/Object.java @@ -36,10 +36,13 @@ *

*
* When preview features are enabled, subclasses of {@code java.lang.Object} - * are either identity classes or {@linkplain Class#isValue value classes}. - * A value object is an instance of a non-abstract value class. - * See The Java Language Specification {@jls value-objects-8.1.1.5 Value Classes}. - * All classes are considered identity classes when preview features are disabled. + * are either {@linkplain Class#isValue value classes} or identity classes. + * A value object is an instance of a non-abstract value class. All + * other objects, including arrays, are identity objects. See + * The Java Language Specification {@jls value-objects-8.1.1.5 Value Classes}. + *

+ * All classes are identity classes and all objects are identity objects when + * preview features are disabled. *

* It is not possible to synchronize on a value object. An attempt to {@code * synchronize} on a value object causes {@link IdentityException} to be thrown. @@ -194,10 +197,8 @@ public boolean equals(Object obj) { /** * Creates and returns a copy of this object. The precise meaning - * of "copy" depends on the class of the object. - *

- * For an {@linkplain java.util.Objects#hasIdentity(Object) identity object} - * {@code x}, the general intent is that the expression: + * of "copy" may depend on the class of the object. The general + * intent is that, for any identity object {@code x}, the expression: *

*
      * x.clone() != x
@@ -205,26 +206,22 @@ public boolean equals(Object obj) { *
*
      * x.clone().getClass() == x.getClass()
- * will be {@code true}, but these are not absolute requirements. - *

+ * will be {@code true} for any object, but these are not absolute requirements. * While it is typically the case that: *

*
      * x.clone().equals(x)
* will be {@code true}, this is not an absolute requirement. *

- * For a value object {@code x} the expectation that {@code x.clone() != x} is - * not meaningful. + * By convention, the {@code clone} method of an identity class should return an + * object obtained by calling {@code super.clone}. If a class and all of its + * superclasses (except {@code Object}) obey this convention, it will be the + * case that {@code x.clone().getClass() == x.getClass()}. *

- * By convention, the returned object should be obtained by calling - * {@code super.clone}. If a class and all of its superclasses (except - * {@code Object}) obey this convention, it will be the case that - * {@code x.clone().getClass() == x.getClass()}. - *

- * By convention, invoking this method on an identity object should return an - * object that is independent of this object (which is being cloned). To achieve - * this independence, it may be necessary to modify one or more fields of the object - * returned by {@code super.clone} before returning it. Typically, this means + * By convention, the object returned by this method should be independent + * of this object (which is being cloned). To achieve this independence, + * it may be necessary to modify one or more fields of the object returned + * by {@code super.clone} before returning it. Typically, this means * copying any mutable objects that comprise the internal "deep structure" * of the object being cloned and replacing the references to these * objects with references to the copies. If a class contains only @@ -232,9 +229,17 @@ public boolean equals(Object obj) { * the case that no fields in the object returned by {@code super.clone} * need to be modified. *

- * Value classes that store references to identity objects may wish to - * override the {@code clone} method and perform a "deep copy" of the - * identity objects. + * For a value object {@code x}, the expectation that {@code x.clone() != x} is not + * meaningful. Value classes that contain references to identity objects may override + * {@code clone} to perform a "deep copy" of the identity objects, returning an object + * with references to the copied identity objects. + * + * @apiNote + * It should be rare for new classes to implement the {@link Cloneable} interface. + * Copy constructors and static factory methods provide a more explicit and flexible + * means of creating copies, allowing classes to define and document their copying + * semantics without the constraints imposed by {@code Cloneable} interface and + * the {@code clone} method. * * @implSpec * The method {@code clone} for class {@code Object} performs a @@ -245,14 +250,14 @@ public boolean equals(Object obj) { * the return type of the {@code clone} method of an array type {@code T[]} * is {@code T[]} where T is any reference or primitive type. *

- * For an identity object, this method creates a new instance of - * the class of this object and initializes all its fields with exactly the - * contents of the corresponding fields of this object, as if by assignment; the + * For an identity object, this method creates a new instance of the class of + * this object and initializes all its fields with exactly the contents of + * the corresponding fields of this object, as if by assignment; the * contents of the fields are not themselves cloned. Thus, this method * performs a "shallow copy" of this object, not a "deep copy" operation. *

- * For a value object, this method simply returns an object that is - * indistinguishable from the original ({@code x.clone() == x}). + * For a value object, this method returns an object that is indistinguishable + * from the original. *

* The class {@code Object} does not itself implement the interface * {@code Cloneable}, so calling the {@code clone} method on an object diff --git a/test/jdk/java/lang/Object/ValueObjects.java b/test/jdk/java/lang/Object/ValueObjects.java new file mode 100644 index 00000000000..9315eca8a1d --- /dev/null +++ b/test/jdk/java/lang/Object/ValueObjects.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2026, 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 test of Object methods on value objects + * @enablePreview + * @run junit ${test.main.class} + */ + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.*; + +class ValueObjects { + + /** + * Test the Object.clone method on a value object. It should return an object that + * is indistinguishable from the original. + */ + @Test + void testClone() throws Exception { + value class V implements Cloneable { + @Override + protected V clone() throws CloneNotSupportedException { + return (V) super.clone(); + } + } + + var obj = new V(); + assertSame(obj, obj.clone()); + } + + /** + * Test the Object.clone method on a value object when the value class does + * not implement Cloneable. + */ + @Test + void testCloneNotSupportedException() throws Exception { + value class V { + @Override + protected V clone() throws CloneNotSupportedException { + return (V) super.clone(); + } + } + + var obj = new V(); + assertThrows(CloneNotSupportedException.class, obj::clone); + } + + /** + * Test that the finalize method on a value object is not invoked by the GC. + */ + @Test + void testFinalize() throws Exception { + value class V { + CountDownLatch latch; + V(CountDownLatch latch) { + this.latch = latch; + } + @Override + protected void finalize() { + latch.countDown(); + } + } + + var latch = new CountDownLatch(1); + var obj = new V(latch); + obj = null; + for (int i = 0; i < 3; i++) { + System.gc(); + // latch should not count down + assertFalse(latch.await(1, TimeUnit.SECONDS)); + } + } + + /** + * Test that the wait/notify methods on a value object throw IMSE. + */ + @Test + void testWaitNotify() { + value class V {} + Object obj = new V(); + assertThrows(IllegalMonitorStateException.class, () -> obj.wait()); + assertThrows(IllegalMonitorStateException.class, () -> obj.wait(1000)); + assertThrows(IllegalMonitorStateException.class, () -> obj.wait(1000, 10)); + assertThrows(IllegalMonitorStateException.class, () -> obj.notify()); + assertThrows(IllegalMonitorStateException.class, () -> obj.notifyAll()); + } + + /** + * Test default toString method. + */ + @Test + void testToString() { + value class V { } + var obj = new V(); + String expected = V.class.getName() + "@" + Integer.toHexString(obj.hashCode()); + assertEquals(expected, obj.toString()); + } +} From 780a704ea5f1e31a5e03896659e9f78f306225ec Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Mon, 29 Jun 2026 16:22:34 +0100 Subject: [PATCH 5/6] Fix finalize spec --- .../share/classes/java/lang/Object.java | 15 ++++---- test/jdk/java/lang/Object/ValueObjects.java | 35 +++++++++++++++++-- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/java.base/share/classes/java/lang/Object.java b/src/java.base/share/classes/java/lang/Object.java index 4e76263d3ff..74e8e89de68 100644 --- a/src/java.base/share/classes/java/lang/Object.java +++ b/src/java.base/share/classes/java/lang/Object.java @@ -47,7 +47,7 @@ * It is not possible to synchronize on a value object. An attempt to {@code * synchronize} on a value object causes {@link IdentityException} to be thrown. *

- * The {@link #finalize()} method of a value object will never be invoked by + * The {@link #finalize()} method of a value class will never be invoked by * the garbage collector. *

* A {@linkplain java.lang.ref.Reference Reference Object} can only refer to an @@ -590,15 +590,18 @@ public final void wait(long timeoutMillis, int nanos) throws InterruptedExceptio } /** - * Called by the garbage collector on an {@linkplain - * java.util.Objects#hasIdentity(Object) identity object} when garbage collection + * Called by the garbage collector on an identity object when garbage collection * determines that there are no more references to the object. - * A subclass overrides the {@code finalize} method to dispose of + * An identity class may override the {@code finalize} method to dispose of * system resources or to perform other cleanup. *

*
- * The {@link #finalize()} method of a value object will never be invoked - * by the garbage collector. + * The {@code finalize} method of a value class is never directly + * invoked by the garbage collector. This includes the case where an + * abstract value class declares a {@code finalize} method and the + * class is extended by an identity class; the garbage collector never + * directly invokes the {@code finalize} method declared by the + * abstract value class. *
*
*

diff --git a/test/jdk/java/lang/Object/ValueObjects.java b/test/jdk/java/lang/Object/ValueObjects.java index 9315eca8a1d..b13fb0d73a7 100644 --- a/test/jdk/java/lang/Object/ValueObjects.java +++ b/test/jdk/java/lang/Object/ValueObjects.java @@ -72,10 +72,10 @@ protected V clone() throws CloneNotSupportedException { } /** - * Test that the finalize method on a value object is not invoked by the GC. + * Test that the finalize method on a value class is not invoked by the GC. */ @Test - void testFinalize() throws Exception { + void testValueClassFinalize() throws Exception { value class V { CountDownLatch latch; V(CountDownLatch latch) { @@ -97,6 +97,37 @@ protected void finalize() { } } + /** + * Test that the finalize method on an abstract value value is not invoked by the GC. + */ + @Test + void testAbstractValueClassFinalize() throws Exception { + abstract value class AV { + CountDownLatch latch; + AV(CountDownLatch latch) { + this.latch = latch; + } + @Override + protected void finalize() { + latch.countDown(); + } + } + /*identity*/ class C extends AV { + C(CountDownLatch latch) { + super(latch); + } + } + + var latch = new CountDownLatch(1); + var obj = new C(latch); + obj = null; + for (int i = 0; i < 3; i++) { + System.gc(); + // latch should not count down + assertFalse(latch.await(1, TimeUnit.SECONDS)); + } + } + /** * Test that the wait/notify methods on a value object throw IMSE. */ From 5a280a9853dd9be264c33ef26aa4bc6d4ae9e975 Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Mon, 29 Jun 2026 19:12:19 +0100 Subject: [PATCH 6/6] Adjust timeout --- test/jdk/java/lang/Object/ValueObjects.java | 25 +++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/test/jdk/java/lang/Object/ValueObjects.java b/test/jdk/java/lang/Object/ValueObjects.java index b13fb0d73a7..f84cee73a99 100644 --- a/test/jdk/java/lang/Object/ValueObjects.java +++ b/test/jdk/java/lang/Object/ValueObjects.java @@ -25,6 +25,7 @@ * @test * @summary Basic test of Object methods on value objects * @enablePreview + * @library /test/lib * @run junit ${test.main.class} */ @@ -35,6 +36,8 @@ import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.*; +import jdk.test.lib.Utils; + class ValueObjects { /** @@ -87,13 +90,13 @@ protected void finalize() { } } - var latch = new CountDownLatch(1); + var latch = new TimeoutAdjustingLatch(); var obj = new V(latch); obj = null; for (int i = 0; i < 3; i++) { System.gc(); // latch should not count down - assertFalse(latch.await(1, TimeUnit.SECONDS)); + assertFalse(latch.await(100, TimeUnit.MILLISECONDS)); } } @@ -118,13 +121,13 @@ protected void finalize() { } } - var latch = new CountDownLatch(1); + var latch = new TimeoutAdjustingLatch(); var obj = new C(latch); obj = null; for (int i = 0; i < 3; i++) { System.gc(); // latch should not count down - assertFalse(latch.await(1, TimeUnit.SECONDS)); + assertFalse(latch.await(100, TimeUnit.MILLISECONDS)); } } @@ -152,4 +155,18 @@ value class V { } String expected = V.class.getName() + "@" + Integer.toHexString(obj.hashCode()); assertEquals(expected, obj.toString()); } + + /** + * A CountDownLatch that is created with a count of 1 and with a timed-await method + * that adjusts the timeout based on the jtreg timeout factory configuration. + */ + private static class TimeoutAdjustingLatch extends CountDownLatch { + TimeoutAdjustingLatch() { + super(1); + } + @Override + public boolean await(long timeout, TimeUnit unit) throws InterruptedException { + return super.await(Utils.adjustTimeout(timeout), unit); + } + } }