diff --git a/src/java.base/share/classes/java/lang/Object.java b/src/java.base/share/classes/java/lang/Object.java
index 705c524eb75..74e8e89de68 100644
--- a/src/java.base/share/classes/java/lang/Object.java
+++ b/src/java.base/share/classes/java/lang/Object.java
@@ -35,12 +35,24 @@
*
*
*
*
*
@@ -186,7 +198,7 @@ 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 any identity object {@code x}, the expression:
*
*
* x.clone() != x
@@ -194,17 +206,17 @@ 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.
*
- * 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 {@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 object returned by this method should be independent
* of this object (which is being cloned). To achieve this independence,
@@ -216,6 +228,18 @@ public boolean equals(Object obj) {
* 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.
+ *
+ * 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
@@ -225,12 +249,16 @@ 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
+ *
+ * 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 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
* whose class is {@code Object} will result in throwing an
@@ -310,14 +338,15 @@ public String toString() {
* Only one thread at a time can own an object's monitor.
*
*
*
*
* @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()
*/
@@ -343,14 +372,15 @@ public String toString() {
*
*
*
*
*
* @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()
*/
@@ -367,14 +397,15 @@ public String toString() {
*
*
*
*
*
* @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.
@@ -398,16 +429,17 @@ public final void wait() throws InterruptedException {
*
*
*
*
*
* @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.
@@ -502,8 +534,10 @@ public final void wait(long timeoutMillis) throws InterruptedException {
*
*
*
*
*
@@ -529,8 +563,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.
@@ -557,14 +590,24 @@ 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 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.
+ *
+ *
+ *
*
* 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.
*
@@ -607,13 +650,6 @@ public final void wait(long timeoutMillis, int nanos) throws InterruptedExceptio
* the finalization of this object to be halted, but is otherwise
* ignored.
*
- *
- *
- *
- *
* @apiNote
* Classes that embed non-heap resources have many options
* for cleanup of those resources. The class must ensure that the
diff --git a/test/jdk/java/lang/Object/ValueObjects.java b/test/jdk/java/lang/Object/ValueObjects.java
new file mode 100644
index 00000000000..f84cee73a99
--- /dev/null
+++ b/test/jdk/java/lang/Object/ValueObjects.java
@@ -0,0 +1,172 @@
+/*
+ * 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
+ * @library /test/lib
+ * @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.*;
+
+import jdk.test.lib.Utils;
+
+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 class is not invoked by the GC.
+ */
+ @Test
+ void testValueClassFinalize() throws Exception {
+ value class V {
+ CountDownLatch latch;
+ V(CountDownLatch latch) {
+ this.latch = latch;
+ }
+ @Override
+ protected void finalize() {
+ latch.countDown();
+ }
+ }
+
+ 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(100, TimeUnit.MILLISECONDS));
+ }
+ }
+
+ /**
+ * 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 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(100, TimeUnit.MILLISECONDS));
+ }
+ }
+
+ /**
+ * 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());
+ }
+
+ /**
+ * 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);
+ }
+ }
+}
+ * 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. + *
+ * 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 + * object with identity. Creating a reference object with a value object as + * the referent throws {@code IdentityException}. *