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
25 /*
26 * @test id=default
27 * @summary Test that identity hash codes are stable across Shenandoah evacuation
28 * @bug 8379910
29 * @requires vm.gc.Shenandoah
30 * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true
31 * @library /test/lib
32 * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseShenandoahGC
33 * -Xms64m -Xmx64m
34 * TestHashCodeEvacRace
35 */
36
37 /*
38 * @test id=generational
39 * @summary Test that identity hash codes are stable across Shenandoah evacuation
40 * @bug 8379910
41 * @requires vm.gc.Shenandoah
42 * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true
43 * @library /test/lib
44 * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseShenandoahGC
45 * -XX:ShenandoahGCMode=generational -Xms64m -Xmx64m
46 * TestHashCodeEvacRace
47 */
48
49 /*
50 * @test id=serial
51 * @summary Test that identity hash codes are stable across Serial GC
52 * @bug 8379910
53 * @requires vm.gc.Serial
54 * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true
55 * @library /test/lib
56 * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseSerialGC
57 * -Xms64m -Xmx64m
58 * TestHashCodeEvacRace
59 */
60
61 /*
62 * @test id=parallel
63 * @summary Test that identity hash codes are stable across Parallel GC
64 * @bug 8379910
65 * @requires vm.gc.Parallel
66 * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true
67 * @library /test/lib
68 * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseParallelGC
69 * -Xms64m -Xmx64m
70 * TestHashCodeEvacRace
71 */
72
73 /*
74 * @test id=g1
75 * @summary Test that identity hash codes are stable across G1 GC
76 * @bug 8379910
77 * @requires vm.gc.G1
78 * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true
79 * @library /test/lib
80 * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseG1GC
81 * -Xms64m -Xmx64m
82 * TestHashCodeEvacRace
83 */
84
85 /*
86 * @test id=zgc
87 * @summary Test that identity hash codes are stable across ZGC
88 * @bug 8379910
89 * @requires vm.gc.Z
90 * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true
91 * @library /test/lib
92 * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseZGC
93 * -Xms64m -Xmx64m
94 * TestHashCodeEvacRace
95 */
96
97 /**
98 * Regression test for a race between reading the identity hash code and
99 * Shenandoah concurrent evacuation with compact object headers.
100 *
101 * With compact headers, objects whose layout has no internal gap for the
102 * identity hash (e.g. a class with a single int field: 4-byte header +
103 * 4-byte int = 8 bytes) require an extra word ("hash expansion") when
104 * evacuated. The bug was that initialize_hash_if_necessary() was called
105 * AFTER the forwarding CAS, leaving a window where the copy is already
106 * visible to other threads (via the forwarding pointer) but the hash
107 * value in the expansion word has not been written yet. A thread reading
108 * the hash during that window would see an uninitialized value.
109 *
110 * The fix moves initialize_hash_if_necessary() before the forwarding CAS
111 * so the copy is fully initialized when it becomes visible.
112 *
113 * This test hashes objects, records the expected values, then continuously
114 * verifies them while GC evacuates the objects. Without the fix, a reader
115 * thread can observe a wrong (uninitialized) hash during evacuation.
116 */
117 public class TestHashCodeEvacRace {
118
119 // With compact headers: 4-byte header + 4-byte int = 8 bytes.
120 // No room for the 4-byte identity hash — requires expansion on evacuation.
121 static class IntHolder {
122 int value;
123 IntHolder(int v) { value = v; }
124 }
125
126 static final int NUM_OBJECTS = 50_000;
127 static final int NUM_READERS = 4;
128 static final int DURATION_MS = 10_000;
129
130 static final IntHolder[] objects = new IntHolder[NUM_OBJECTS];
131 static final int[] expectedHash = new int[NUM_OBJECTS];
132
133 static volatile boolean running = true;
134 static volatile String failure = null;
135
136 public static void main(String[] args) throws Exception {
137 // Create objects and record their identity hash codes.
138 // After this, each object has is_hashed_not_expanded state.
139 for (int i = 0; i < NUM_OBJECTS; i++) {
140 objects[i] = new IntHolder(i);
141 expectedHash[i] = System.identityHashCode(objects[i]);
142 }
143
144 // Reader threads: continuously read identity hash codes and verify
145 // they match the recorded values. During concurrent evacuation,
146 // readers may follow forwarding pointers to to-space copies.
147 // Without the fix, the copy may be visible before its hash is
148 // initialized, causing a mismatch.
149 Thread[] readers = new Thread[NUM_READERS];
150 for (int t = 0; t < NUM_READERS; t++) {
151 readers[t] = new Thread(() -> {
152 while (running && failure == null) {
153 for (int i = 0; i < NUM_OBJECTS; i++) {
154 IntHolder obj = objects[i];
155 if (obj == null) continue;
156 int actual = System.identityHashCode(obj);
157 int expected = expectedHash[i];
158 if (actual != expected) {
159 failure = "Hash mismatch at index " + i
160 + ": expected=" + expected
161 + " actual=" + actual;
162 return;
163 }
164 }
165 }
166 });
167 readers[t].setDaemon(true);
168 readers[t].start();
169 }
170
171 // Main thread: allocate garbage to trigger GC and evacuation.
172 // The objects[] array keeps the hashed objects alive so they get
173 // evacuated rather than collected.
174 long deadline = System.currentTimeMillis() + DURATION_MS;
175 while (System.currentTimeMillis() < deadline && failure == null) {
176 for (int i = 0; i < 100; i++) {
177 byte[] garbage = new byte[4096];
178 }
179 Thread.yield();
180 }
181
182 running = false;
183 for (Thread t : readers) t.join(5000);
184
185 if (failure != null) {
186 throw new RuntimeException(failure);
187 }
188 System.out.println("PASSED");
189 }
190 }