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 }