1 /*
2 * Copyright (c) 2025, Oracle and/or its affiliates. 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 * @test
26 * @summary Tests heapwalking API (FollowReferences, IterateThroughHeap, GetObjectsWithTags) for value objects.
27 * @requires vm.jvmti
28 * @modules java.base/jdk.internal.vm.annotation java.base/jdk.internal.value
29 * @enablePreview
30 * @run main/othervm/native -agentlib:ValueHeapwalkingTest
31 * -XX:+UnlockDiagnosticVMOptions
32 * -XX:+UseArrayFlattening
33 * -XX:+UseFieldFlattening
34 * -XX:+UseAtomicValueFlattening
35 * -XX:+UseNullableValueFlattening
36 * -XX:+PrintInlineLayout
37 * -XX:+PrintFlatArrayLayout
38 * -Xlog:jvmti+table
39 * ValueHeapwalkingTest
40 */
41
42 import java.lang.ref.Reference;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.List;
46 import jdk.internal.value.ValueClass;
47 import jdk.internal.vm.annotation.NullRestricted;
48 import jdk.internal.vm.annotation.Strict;
49
50 import java.lang.reflect.Field;
51
52 public class ValueHeapwalkingTest {
53
54 static value class Value {
55 int v;
56 Value() {
57 this(0);
58 }
59 Value(int v) {
60 this.v = v;
61 }
62 }
63
64 // flat object has flat field (address of the field is the same as address of the object)
65 static value class Value2 {
66 @Strict
67 @NullRestricted
68 public Value v1;
69 @Strict
70 @NullRestricted
71 public Value v2;
72 Value2() {
73 this(0);
74 }
75 Value2(int i) {
76 this.v1 = new Value(i);
77 this.v2 = new Value(i+1);
78 super();
79 }
80 }
81
82 static value class ValueHolder {
83 public Value v1;
84 @Strict
85 @NullRestricted
86 public Value v2;
87 public Value v_null;
88
89 public Value2 v2_1;
90 @Strict
91 @NullRestricted
92 public Value2 v2_2;
93
94 public Value[] v_arr;
95 public Value2[] v2_arr;
96
97 public ValueHolder(int seed) throws Exception {
98 v1 = new Value(seed);
99 v2 = new Value(seed + 1);
100 v_null = null;
101
102 v2_1 = new Value2(seed + 6);
103 v2_2 = new Value2(seed + 8);
104
105 v_arr = createValueArray(seed);
106 v2_arr = createValue2Array(seed);
107 }
108 }
109
110 static Value[] createValueArray(int seed) throws Exception {
111 Value[] arr = (Value[])ValueClass.newNullableAtomicArray(Value.class, 5);
112 for (int i = 0; i < arr.length; i++) {
113 arr[i] = i == 2 ? null : new Value(seed + 10 + i);
114 }
115 return arr;
116 }
117
118 static Value2[] createValue2Array(int seed) throws Exception {
119 Value2[] arr = (Value2[])ValueClass.newNullRestrictedNonAtomicArray(Value2.class, 5, Value2.class.newInstance());
120 for (int i = 0; i < arr.length; i++) {
121 arr[i] = new Value2(seed + 20 + i * 2);
122 }
123 return arr;
124 }
125
126 static final int TAG_VALUE_CLASS = 1;
127 static final int TAG_VALUE2_CLASS = 2;
128 static final int TAG_HOLDER_CLASS = 3;
129 static final int TAG_VALUE_ARRAY = 4;
130 static final int TAG_VALUE2_ARRAY = 5;
131
132 static final int TAG_MIN = TAG_VALUE_CLASS;
133 static final int TAG_MAX = TAG_VALUE2_ARRAY;
134
135 static String tagStr(int tag) {
136 String suffix = " (tag " + tag + ")";
137 switch (tag) {
138 case TAG_VALUE_CLASS: return "Value class" + suffix;
139 case TAG_VALUE2_CLASS: return "Value2 class" + suffix;
140 case TAG_HOLDER_CLASS: return "ValueHolder class" + suffix;
141 case TAG_VALUE_ARRAY: return "Value[] object" + suffix;
142 case TAG_VALUE2_ARRAY: return "Value2[] object" + suffix;
143 }
144 return "Unknown" + suffix;
145 }
146
147 static native void setTag(Object object, long tag);
148 static native long getTag(Object object);
149
150 static native void reset();
151
152 static native void followReferences();
153
154 static native void iterateThroughHeap();
155
156 static native int count(int classTag);
157 static native int refCount(int fromTag, int toTag);
158 static native int primitiveFieldCount(int tag);
159
160 static native long getMaxTag();
161
162 static native int getObjectWithTags(long minTag, long maxTag, Object[] objects, long[] tags);
163
164
165 // counts non-null elements in the array
166 static <T> int nonNullCount(T[] array) {
167 return (int)Arrays.stream(array).filter(e -> e != null).count();
168 }
169
170 static void verifyMinCount(int classTag, int minCount) {
171 int count = count(classTag);
172 String msg = tagStr(classTag) + " count: " + count + ", min expected: " + minCount;
173 if (count < minCount) {
174 throw new RuntimeException(msg);
175 }
176 System.out.println(msg);
177 }
178
179 static void verifyRefCount(int tagFrom, int tagTo, int expectedCount) {
180 int count = refCount(tagFrom, tagTo);
181 String msg = "Ref.count from " + tagStr(tagFrom) + " to " + tagStr(tagTo) + ": "
182 + count + ", expected: " + expectedCount;
183 if (count != expectedCount) {
184 throw new RuntimeException(msg);
185 }
186 System.out.println(msg);
187 }
188
189 static void verifyPrimitiveFieldCount(int classTag, int expectedCount) {
190 int count = primitiveFieldCount(classTag);
191 String msg = "Primitive field count from " + tagStr(classTag) + ": "
192 + count + ", expected: " + expectedCount;
193 if (count != expectedCount) {
194 throw new RuntimeException(msg);
195 }
196 System.out.println(msg);
197 }
198
199
200 static void printObject(Object obj) {
201 printObject("", obj);
202 }
203
204 static void printObject(String prefix, Object obj) {
205 if (obj == null) {
206 System.out.println(prefix + "null");
207 return;
208 }
209
210 Class<?> clazz = obj.getClass();
211 System.out.println(prefix + "Object (class " + clazz.getName() + ", tag = " + getTag(obj) + ", class tag = " + getTag(clazz));
212
213 if (clazz.isArray()) {
214 Class<?> elementType = clazz.getComponentType();
215 int length = java.lang.reflect.Array.getLength(obj);
216 System.out.println(prefix + "Array of " + elementType + ", length = " + length + " [");
217 for (int i = 0; i < length; i++) {
218 Object element = java.lang.reflect.Array.get(obj, i);
219 if (elementType.isPrimitive()) {
220 if (i == 0) {
221 System.out.print(prefix + " ");
222 } else {
223 System.out.print(", ");
224 }
225 System.out.print(prefix + "(" + i + "):" + element);
226 } else {
227 System.out.println(prefix + "(" + i + "):" + "NOT primitive");
228 printObject(prefix + " ", element);
229 }
230 }
231 System.out.println(prefix + "]");
232 } else {
233 while (clazz != null && clazz != Object.class) {
234 Field[] fields = clazz.getDeclaredFields();
235 for (Field field : fields) {
236 try {
237 field.setAccessible(true);
238 Object value = field.get(obj);
239 Class<?> fieldType = field.getType();
240 System.out.println(prefix + "- " + field.getName() + " (" + fieldType + ") = " + value);
241
242 if (!fieldType.isPrimitive()) {
243 printObject(prefix + " ", value);
244 }
245 } catch (IllegalAccessException | java.lang.reflect.InaccessibleObjectException e) {
246 System.err.println(" Error accessing field " + field.getName() + ": " + e.getMessage());
247 }
248 }
249 clazz = clazz.getSuperclass();
250 }
251 }
252 }
253
254 public static void main(String[] args) throws Exception {
255 System.loadLibrary("ValueHeapwalkingTest");
256 ValueHolder holder = new ValueHolder(10);
257
258 setTag(Value.class, TAG_VALUE_CLASS);
259 setTag(Value2.class, TAG_VALUE2_CLASS);
260 setTag(ValueHolder.class, TAG_HOLDER_CLASS);
261 setTag(holder.v_arr, TAG_VALUE_ARRAY);
262 setTag(holder.v2_arr, TAG_VALUE2_ARRAY);
263
264 reset();
265 System.out.println(">>iterateThroughHeap");
266 iterateThroughHeap();
267 System.out.println("<<iterateThroughHeap");
268
269 // IterateThroughHeap reports reachable and unreachable objects,
270 // so verify only minimum count.
271 for (int i = TAG_MIN; i <= TAG_MAX; i++) {
272 System.out.println(tagStr(i) + " count: " + count(i));
273 }
274 int reachableValueHolderCount = 1;
275 // v2_1, v2_2, v2_arr
276 int reachableValue2Count = reachableValueHolderCount * (2 + nonNullCount(holder.v2_arr));
277 // ValueHolder: v1, v2, v_arr
278 // Value2: v1, v2
279 int reachableValueCount = reachableValueHolderCount * (2 + nonNullCount(holder.v_arr))
280 + reachableValue2Count * 2;
281 verifyMinCount(TAG_VALUE_CLASS, reachableValueCount);
282 verifyMinCount(TAG_VALUE2_CLASS, reachableValue2Count);
283 verifyMinCount(TAG_HOLDER_CLASS, reachableValueHolderCount);
284 // For each Value object 1 primitive field must be reported.
285 verifyPrimitiveFieldCount(TAG_VALUE_CLASS, count(TAG_VALUE_CLASS));
286
287 reset(); // to reset primitiveFieldCount
288 System.out.println(">>followReferences");
289 followReferences();
290 System.out.println("<<followReferences");
291
292 long maxTag = getMaxTag();
293
294 for (int i = TAG_MIN; i <= TAG_MAX; i++) {
295 for (int j = TAG_MIN; j <= TAG_MAX; j++) {
296 System.out.println("Reference from " + tagStr(i) + " to " + tagStr(j) + ": " + refCount(i, j));
297 }
298 }
299
300 printObject(holder);
301
302 // ValueHolder: v1, v2
303 verifyRefCount(TAG_HOLDER_CLASS, TAG_VALUE_CLASS, 2);
304 // ValueHolder: v2_1, v2_2
305 verifyRefCount(TAG_HOLDER_CLASS, TAG_VALUE_CLASS, 2);
306 // ValueHolder: v_arr
307 verifyRefCount(TAG_HOLDER_CLASS, TAG_VALUE_ARRAY, 1);
308 // ValueHolder: v2_arr
309 verifyRefCount(TAG_HOLDER_CLASS, TAG_VALUE2_ARRAY, 1);
310
311 // References from arrays to their elements
312 verifyRefCount(TAG_VALUE_ARRAY, TAG_VALUE_CLASS, nonNullCount(holder.v_arr));
313 verifyRefCount(TAG_VALUE2_ARRAY, TAG_VALUE2_CLASS, nonNullCount(holder.v2_arr));
314
315 // Each Value2 object must have 2 references to Value object (v1, v2).
316 verifyRefCount(TAG_VALUE2_CLASS, TAG_VALUE_CLASS, reachableValue2Count * 2);
317
318 // For each Value object 1 primitive field must be reported.
319 verifyPrimitiveFieldCount(TAG_VALUE_CLASS, reachableValueCount);
320
321 System.out.println(">>followReferences (2)");
322 followReferences();
323 System.out.println("<<followReferences (2)");
324
325 long maxTag2 = getMaxTag();
326 // no new objects are expected to be tagged
327 if (maxTag != maxTag2) {
328 throw new RuntimeException("maxTag (" + maxTag + ") not equal to maxTag2(" + maxTag2 + ")");
329 }
330
331 Object[] objects = new Object[1024];
332 long tags[] = new long[1024];
333 System.out.println(">>getObjectWithTags, maxTag = " + maxTag);
334 int count = getObjectWithTags(1, maxTag, objects, tags);
335 System.out.println("getObjectWithTags returned " + count);
336 for (int i = 0; i < count; i++) {
337 System.out.println(" [" + i + "] " + objects[i] + ", tag = " + tags[i]);
338 if (objects[i] == null || tags[i] == 0) {
339 throw new RuntimeException("unexpected object");
340 }
341 }
342 int expectedTaggedCount = 5 // TAG_VALUE_CLASS/TAG_VALUE2_CLASS/TAG_HOLDER_CLASS/TAG_VALUE_ARRAY/TAG_VALUE2_ARRAY
343 + reachableValue2Count + reachableValueCount;
344 if (count != expectedTaggedCount) {
345 throw new RuntimeException("unexpected getObjectWithTags result: " + count
346 + ", expected " + expectedTaggedCount);
347 }
348
349 Reference.reachabilityFence(holder);
350 }
351 }