1 /*
2 * Copyright (c) 2026, Datadog, Inc. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 package gc.stress.ihash;
25
26 /*
27 * @test id=Serial
28 * @bug 8372151
29 * @summary Stress test: cloning identity-hashed objects must not copy mark-word hash-control bits
30 * @requires vm.gc.Serial
31 * @key stress
32 * @run main/othervm/timeout=300
33 * -XX:+UseCompactObjectHeaders -XX:+UseSerialGC
34 * -XX:+UnlockDiagnosticVMOptions -XX:+VerifyDuringGC
35 * -Xmx256m
36 * gc.stress.ihash.TestStressIHash
37 */
38
39 /*
40 * @test id=Parallel
41 * @bug 8372151
42 * @summary Stress test: cloning identity-hashed objects must not copy mark-word hash-control bits
43 * @requires vm.gc.Parallel
44 * @key stress
45 * @run main/othervm/timeout=300
46 * -XX:+UseCompactObjectHeaders -XX:+UseParallelGC
47 * -XX:+UnlockDiagnosticVMOptions -XX:+VerifyDuringGC
48 * -Xmx256m
49 * gc.stress.ihash.TestStressIHash
50 */
51
52 /*
53 * @test id=G1
54 * @bug 8372151
55 * @summary Stress test: cloning identity-hashed objects must not copy mark-word hash-control bits
56 * @requires vm.gc.G1
57 * @key stress
58 * @run main/othervm/timeout=300
59 * -XX:+UseCompactObjectHeaders -XX:+UseG1GC
60 * -XX:+UnlockDiagnosticVMOptions -XX:+VerifyDuringGC
61 * -Xmx256m
62 * gc.stress.ihash.TestStressIHash
63 */
64
65 /*
66 * @test id=Shenandoah
67 * @bug 8372151
68 * @summary Stress test: cloning identity-hashed objects must not copy mark-word hash-control bits
69 * @requires vm.gc.Shenandoah
70 * @key stress
71 * @run main/othervm/timeout=300
72 * -XX:+UseCompactObjectHeaders -XX:+UseShenandoahGC
73 * -XX:+UnlockDiagnosticVMOptions -XX:+VerifyDuringGC
74 * -Xmx256m
75 * gc.stress.ihash.TestStressIHash
76 */
77
78 /*
79 * @test id=Z
80 * @bug 8372151
81 * @summary Stress test: cloning identity-hashed objects must not copy mark-word hash-control bits
82 * @requires vm.gc.Z
83 * @key stress
84 * @run main/othervm/timeout=300
85 * -XX:+UseCompactObjectHeaders -XX:+UseZGC
86 * -XX:+UnlockDiagnosticVMOptions -XX:+VerifyDuringGC
87 * -Xmx256m
88 * gc.stress.ihash.TestStressIHash
89 */
90
91 import java.util.Random;
92
93 /**
94 * Regression test for JDK-8372151.
95 *
96 * With compact object headers (4-byte mark-word), the identity hash state is
97 * tracked via two "hashctrl" bits in the mark-word. When an object is hashed
98 * and later relocated by GC, the GC may "expand" it by one HeapWord to store
99 * the hash value, setting the hashctrl bits to "hashed-expanded" (11).
100 *
101 * The bug: Object.clone() copied the mark-word from source to destination,
102 * including the hashctrl bits. A clone of an expanded object would therefore
103 * appear expanded (hashctrl=11) without actually having the extra HeapWord
104 * allocated. This causes two problems:
105 * 1. The clone's identity hash is the same as the source's (semantic bug).
106 * 2. JVM_Clone allocates the clone at expanded size but then resets the
107 * mark-word to prototype (not-hashed, not-expanded), creating a size
108 * mismatch that crashes GCs using linear heap iteration (e.g., Serial).
109 *
110 * This test creates objects whose layout guarantees hash-expansion (a single
111 * int field: header(4) + int(4) = 8 bytes, no room for the 4-byte hash),
112 * hashes them, forces a GC to expand them, then clones them. It verifies
113 * both that clones get unique identity hashes (semantic correctness) and that
114 * the heap remains intact (-XX:+VerifyDuringGC detects size mismatches).
115 */
116 public class TestStressIHash {
117
118 // With compact headers: header(4) + int(4) = 8 bytes (1 HeapWord).
119 // The identity hash needs 4 bytes but there is no gap in the layout,
120 // so GC must expand the object by one word when preserving the hash.
121 static class Payload implements Cloneable {
122 int field;
123
124 Payload(int v) { field = v; }
125
126 @Override
127 public Payload clone() {
128 try {
129 return (Payload) super.clone();
130 } catch (CloneNotSupportedException e) {
131 throw new RuntimeException(e);
132 }
133 }
134 }
135
136 static final int BATCH_SIZE = 100_000;
137 static final int MAX_SURVIVORS = 1_000_000;
138
139 public static void main(String[] args) {
140 Random rng = new Random(12345);
141 Object[] survivors = new Object[MAX_SURVIVORS];
142 int survivorCount = 0;
143 int totalCreated = 0;
144 int sharedHashes = 0;
145 int totalCloned = 0;
146
147 while (survivorCount < MAX_SURVIVORS) {
148 // Phase 1: Create a batch of objects and hash every one of them.
149 Payload[] batch = new Payload[BATCH_SIZE];
150 int[] srcHashes = new int[BATCH_SIZE];
151 for (int i = 0; i < BATCH_SIZE; i++) {
152 batch[i] = new Payload(totalCreated++);
153 srcHashes[i] = System.identityHashCode(batch[i]);
154 }
155
156 // Phase 2: Force a GC cycle. Surviving hashed objects that need
157 // expansion get relocated with an extra HeapWord and their
158 // hashctrl bits set to "hashed-expanded" (11).
159 System.gc();
160
161 // Phase 3: Clone the (now expanded) batch objects and verify that
162 // each clone gets a unique identity hash. With the bug, clones
163 // inherit the source's hash because the hashctrl bits and stored
164 // hash value are copied from the source mark-word.
165 for (int i = 0; i < BATCH_SIZE; i++) {
166 Payload clone = batch[i].clone();
167 totalCloned++;
168 int cloneHash = System.identityHashCode(clone);
169 if (cloneHash == srcHashes[i]) {
170 sharedHashes++;
171 }
172 if (rng.nextInt(10) == 0 && survivorCount < MAX_SURVIVORS) {
173 survivors[survivorCount++] = clone;
174 }
175 }
176
177 // Let the originals die.
178 batch = null;
179 }
180
181 // Final GC: with VerifyDuringGC, this walks the heap and will detect
182 // corrupt clones if the allocation-size vs mark-word bug is present.
183 System.gc();
184
185 // Verify that clones got independent identity hashes. Random hash
186 // collisions are possible but extremely rare (~1 in 2^32 per pair).
187 // The threshold of 10 accounts for any statistical noise.
188 if (sharedHashes > 10) {
189 throw new RuntimeException("FAIL: " + sharedHashes + " / " + totalCloned
190 + " clones share identity hash with source object");
191 }
192 }
193 }