1 /* 2 * Copyright (c) 1997, 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 /* 26 * The Original Code is HAT. The Initial Developer of the 27 * Original Code is Bill Foote, with contributions from others 28 * at JavaSoft/Sun. 29 */ 30 31 package jdk.test.lib.hprof.model; 32 33 import java.io.IOException; 34 import java.lang.ref.SoftReference; 35 import java.util.*; 36 37 import jdk.test.lib.hprof.parser.ReadBuffer; 38 import jdk.test.lib.hprof.util.Misc; 39 40 /** 41 * Represents a snapshot of the Java objects in the VM at one instant. 42 * This is the top-level "model" object read out of a single .hprof or .bod 43 * file. 44 */ 45 46 public class Snapshot implements AutoCloseable { 47 48 public static final long SMALL_ID_MASK = 0x0FFFFFFFFL; 49 public static final JavaThing[] EMPTY_JAVATHING_ARRAY = new JavaThing[0]; 50 51 private static final JavaField[] EMPTY_FIELD_ARRAY = new JavaField[0]; 52 private static final JavaStatic[] EMPTY_STATIC_ARRAY = new JavaStatic[0]; 53 54 // all heap objects 55 private Hashtable<Number, JavaHeapObject> heapObjects = 56 new Hashtable<Number, JavaHeapObject>(); 57 58 private Hashtable<Number, JavaClass> fakeClasses = 59 new Hashtable<Number, JavaClass>(); 60 61 // all Roots in this Snapshot 62 private Vector<Root> roots = new Vector<Root>(); 63 64 // name-to-class map 65 private Map<String, JavaClass> classes = 66 new TreeMap<String, JavaClass>(); 67 68 private Map<Number, Number> flatArrays = new HashMap<>(); 69 70 private Map<Number, ClassInlinedFields[]> inlinedFields = new HashMap<>(); 71 72 // new objects relative to a baseline - lazily initialized 73 private volatile Map<JavaHeapObject, Boolean> newObjects; 74 75 // allocation site traces for all objects - lazily initialized 76 private volatile Map<JavaHeapObject, StackTrace> siteTraces; 77 78 // object-to-Root map for all objects 79 private Map<JavaHeapObject, Root> rootsMap = 80 new HashMap<JavaHeapObject, Root>(); 81 82 // soft cache of finalizeable objects - lazily initialized 83 private SoftReference<Vector<?>> finalizablesCache; 84 85 // threads 86 private ArrayList<ThreadObject> threads = new ArrayList<>(); 87 88 // represents null reference 89 private JavaThing nullThing; 90 91 // java.lang.ref.Reference class 92 private JavaClass weakReferenceClass; 93 // index of 'referent' field in java.lang.ref.Reference class 94 private int referentFieldIndex; 95 96 // java.lang.Class class 97 private JavaClass javaLangClass; 98 // java.lang.String class 99 private JavaClass javaLangString; 100 // java.lang.ClassLoader class 101 private JavaClass javaLangClassLoader; 102 103 // unknown "other" array class 104 private volatile JavaClass otherArrayType; 105 // Stuff to exclude from reachable query 106 private ReachableExcludes reachableExcludes; 107 // the underlying heap dump buffer 108 private ReadBuffer readBuf; 109 110 // True iff some heap objects have isNew set 111 private boolean hasNewSet; 112 private boolean unresolvedObjectsOK; 113 114 // whether object array instances have new style class or 115 // old style (element) class. 116 private boolean newStyleArrayClass; 117 118 // object id size in the heap dump 119 private int identifierSize = 4; 120 121 // minimum object size - accounts for object header in 122 // most Java virtual machines - we assume 2 identifierSize 123 // (which is true for Sun's hotspot JVM). 124 private int minimumObjectSize; 125 126 public Snapshot(ReadBuffer buf) { 127 nullThing = new HackJavaValue("<null>", 0); 128 readBuf = buf; 129 } 130 131 public void setSiteTrace(JavaHeapObject obj, StackTrace trace) { 132 if (trace != null && trace.getFrames().length != 0) { 133 initSiteTraces(); 134 siteTraces.put(obj, trace); 135 } 136 } 137 138 public StackTrace getSiteTrace(JavaHeapObject obj) { 139 if (siteTraces != null) { 140 return siteTraces.get(obj); 141 } else { 142 return null; 143 } 144 } 145 146 public void setNewStyleArrayClass(boolean value) { 147 newStyleArrayClass = value; 148 } 149 150 public boolean isNewStyleArrayClass() { 151 return newStyleArrayClass; 152 } 153 154 public void setIdentifierSize(int size) { 155 identifierSize = size; 156 minimumObjectSize = 2 * size; 157 } 158 159 public int getIdentifierSize() { 160 return identifierSize; 161 } 162 163 public int getMinimumObjectSize() { 164 return minimumObjectSize; 165 } 166 167 public void addHeapObject(long id, JavaHeapObject ho) { 168 heapObjects.put(makeId(id), ho); 169 } 170 171 public void addRoot(Root r) { 172 r.setIndex(roots.size()); 173 roots.addElement(r); 174 } 175 176 public void addClass(long id, JavaClass c) { 177 addHeapObject(id, c); 178 putInClassesMap(c); 179 } 180 181 public void addThreadObject(ThreadObject thread) { 182 threads.add(thread); 183 } 184 185 JavaClass addFakeInstanceClass(long classID, int instSize) { 186 // Create a fake class name based on ID. 187 String name = "unknown-class<@" + Misc.toHex(classID) + ">"; 188 189 // Create fake fields convering the given instance size. 190 // Create as many as int type fields and for the left over 191 // size create byte type fields. 192 int numInts = instSize / 4; 193 int numBytes = instSize % 4; 194 JavaField[] fields = new JavaField[numInts + numBytes]; 195 int i; 196 for (i = 0; i < numInts; i++) { 197 fields[i] = new JavaField("unknown-field-" + i, "I"); 198 } 199 for (i = 0; i < numBytes; i++) { 200 fields[i + numInts] = new JavaField("unknown-field-" + 201 i + numInts, "B"); 202 } 203 204 // Create fake instance class 205 JavaClass c = new JavaClass(name, 0, 0, 0, 0, fields, 206 EMPTY_STATIC_ARRAY, instSize); 207 // Add the class 208 addFakeClass(makeId(classID), c); 209 return c; 210 } 211 212 public void addFlatArray(long objID, long elementClassID) { 213 flatArrays.put(makeId(objID), makeId(elementClassID)); 214 } 215 216 /** 217 * @return null if the array is not flat array 218 */ 219 Number findFlatArrayElementType(long arrayObjectID) { 220 return flatArrays.get(makeId(arrayObjectID)); 221 } 222 223 public static class ClassInlinedFields { 224 final int fieldIndex; 225 final int synthFieldCount; 226 final String fieldName; 227 final long fieldClassID; 228 229 public ClassInlinedFields(int fieldIndex, int synthFieldCount, String fieldName, long fieldClassID) { 230 this.fieldIndex = fieldIndex; 231 this.synthFieldCount = synthFieldCount; 232 this.fieldName = fieldName; 233 this.fieldClassID = fieldClassID; 234 } 235 } 236 237 public void addClassInlinedFields(long classID, ClassInlinedFields[] fields) { 238 inlinedFields.put(makeId(classID), fields); 239 } 240 241 /** 242 * @return null if the class has no inlined fields 243 */ 244 ClassInlinedFields[] findClassInlinedFields(long classID) { 245 return inlinedFields.get(makeId(classID)); 246 } 247 248 /** 249 * @return true iff it's possible that some JavaThing instances might 250 * isNew set 251 * 252 * @see JavaThing.isNew() 253 */ 254 public boolean getHasNewSet() { 255 return hasNewSet; 256 } 257 258 // 259 // Used in the body of resolve() 260 // 261 private static class MyVisitor extends AbstractJavaHeapObjectVisitor { 262 JavaHeapObject t; 263 public void visit(JavaHeapObject other) { 264 other.addReferenceFrom(t); 265 } 266 } 267 268 // To show heap parsing progress, we print a '.' after this limit 269 private static final int DOT_LIMIT = 5000; 270 271 /** 272 * Called after reading complete, to initialize the structure 273 */ 274 public void resolve(boolean calculateRefs) { 275 System.out.println("Resolving " + heapObjects.size() + " objects..."); 276 277 // First, resolve the classes. All classes must be resolved before 278 // we try any objects, because the objects use classes in their 279 // resolution. 280 javaLangClass = findClass("java.lang.Class"); 281 if (javaLangClass == null) { 282 System.out.println("WARNING: hprof file does not include java.lang.Class!"); 283 javaLangClass = new JavaClass("java.lang.Class", 0, 0, 0, 0, 284 EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0); 285 addFakeClass(javaLangClass); 286 } 287 javaLangString = findClass("java.lang.String"); 288 if (javaLangString == null) { 289 System.out.println("WARNING: hprof file does not include java.lang.String!"); 290 javaLangString = new JavaClass("java.lang.String", 0, 0, 0, 0, 291 EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0); 292 addFakeClass(javaLangString); 293 } 294 javaLangClassLoader = findClass("java.lang.ClassLoader"); 295 if (javaLangClassLoader == null) { 296 System.out.println("WARNING: hprof file does not include java.lang.ClassLoader!"); 297 javaLangClassLoader = new JavaClass("java.lang.ClassLoader", 0, 0, 0, 0, 298 EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0); 299 addFakeClass(javaLangClassLoader); 300 } 301 302 for (JavaHeapObject t : heapObjects.values()) { 303 if (t instanceof JavaClass) { 304 t.resolve(this); 305 } 306 } 307 308 // Now, resolve everything else. 309 for (JavaHeapObject t : heapObjects.values()) { 310 if (!(t instanceof JavaClass)) { 311 t.resolve(this); 312 } 313 } 314 315 heapObjects.putAll(fakeClasses); 316 fakeClasses.clear(); 317 318 weakReferenceClass = findClass("java.lang.ref.Reference"); 319 referentFieldIndex = 0; 320 if (weakReferenceClass != null) { 321 JavaField[] fields = weakReferenceClass.getFieldsForInstance(); 322 for (int i = 0; i < fields.length; i++) { 323 if ("referent".equals(fields[i].getName())) { 324 referentFieldIndex = i; 325 break; 326 } 327 } 328 } 329 330 if (calculateRefs) { 331 calculateReferencesToObjects(); 332 System.out.print("Eliminating duplicate references"); 333 System.out.flush(); 334 // This println refers to the *next* step 335 } 336 int count = 0; 337 for (JavaHeapObject t : heapObjects.values()) { 338 t.setupReferrers(); 339 ++count; 340 if (calculateRefs && count % DOT_LIMIT == 0) { 341 System.out.print("."); 342 System.out.flush(); 343 } 344 } 345 if (calculateRefs) { 346 System.out.println(""); 347 } 348 349 // to ensure that Iterator.remove() on getClasses() 350 // result will throw exception.. 351 classes = Collections.unmodifiableMap(classes); 352 } 353 354 private void calculateReferencesToObjects() { 355 System.out.print("Chasing references, expect " 356 + (heapObjects.size() / DOT_LIMIT) + " dots"); 357 System.out.flush(); 358 int count = 0; 359 MyVisitor visitor = new MyVisitor(); 360 for (JavaHeapObject t : heapObjects.values()) { 361 visitor.t = t; 362 // call addReferenceFrom(t) on all objects t references: 363 t.visitReferencedObjects(visitor); 364 ++count; 365 if (count % DOT_LIMIT == 0) { 366 System.out.print("."); 367 System.out.flush(); 368 } 369 } 370 System.out.println(); 371 for (Root r : roots) { 372 r.resolve(this); 373 JavaHeapObject t = findThing(r.getId()); 374 if (t != null) { 375 t.addReferenceFromRoot(r); 376 } 377 } 378 } 379 380 public void markNewRelativeTo(Snapshot baseline) { 381 hasNewSet = true; 382 for (JavaHeapObject t : heapObjects.values()) { 383 boolean isNew; 384 long thingID = t.getId(); 385 if (thingID == 0L || thingID == -1L) { 386 isNew = false; 387 } else { 388 JavaThing other = baseline.findThing(t.getId()); 389 if (other == null) { 390 isNew = true; 391 } else { 392 isNew = !t.isSameTypeAs(other); 393 } 394 } 395 t.setNew(isNew); 396 } 397 } 398 399 public Enumeration<JavaHeapObject> getThings() { 400 return heapObjects.elements(); 401 } 402 403 404 public JavaHeapObject findThing(long id) { 405 Number idObj = makeId(id); 406 JavaHeapObject jho = heapObjects.get(idObj); 407 return jho != null? jho : fakeClasses.get(idObj); 408 } 409 410 public JavaHeapObject findThing(String id) { 411 return findThing(Misc.parseHex(id)); 412 } 413 414 public JavaClass findClass(String name) { 415 if (name.startsWith("0x")) { 416 return (JavaClass) findThing(name); 417 } else { 418 return classes.get(name); 419 } 420 } 421 422 /** 423 * Return an Iterator of all of the classes in this snapshot. 424 **/ 425 public Iterator<JavaClass> getClasses() { 426 // note that because classes is a TreeMap 427 // classes are already sorted by name 428 return classes.values().iterator(); 429 } 430 431 public JavaClass[] getClassesArray() { 432 JavaClass[] res = new JavaClass[classes.size()]; 433 classes.values().toArray(res); 434 return res; 435 } 436 437 public synchronized Enumeration<?> getFinalizerObjects() { 438 Vector<?> obj; 439 if (finalizablesCache != null && 440 (obj = finalizablesCache.get()) != null) { 441 return obj.elements(); 442 } 443 444 JavaClass clazz = findClass("java.lang.ref.Finalizer"); 445 JavaObject queue = (JavaObject) clazz.getStaticField("queue"); 446 JavaThing tmp = queue.getField("head"); 447 Vector<JavaHeapObject> finalizables = new Vector<JavaHeapObject>(); 448 if (tmp != getNullThing()) { 449 JavaObject head = (JavaObject) tmp; 450 while (true) { 451 JavaHeapObject referent = (JavaHeapObject) head.getField("referent"); 452 JavaThing next = head.getField("next"); 453 if (next == getNullThing() || next.equals(head)) { 454 break; 455 } 456 head = (JavaObject) next; 457 finalizables.add(referent); 458 } 459 } 460 finalizablesCache = new SoftReference<Vector<?>>(finalizables); 461 return finalizables.elements(); 462 } 463 464 public Enumeration<Root> getRoots() { 465 return roots.elements(); 466 } 467 468 public Root[] getRootsArray() { 469 Root[] res = new Root[roots.size()]; 470 roots.toArray(res); 471 return res; 472 } 473 474 public Root getRootAt(int i) { 475 return roots.elementAt(i); 476 } 477 478 public List<ThreadObject> getThreads() { 479 return Collections.unmodifiableList(threads); 480 } 481 482 public ReferenceChain[] 483 rootsetReferencesTo(JavaHeapObject target, boolean includeWeak) { 484 Vector<ReferenceChain> fifo = new Vector<ReferenceChain>(); // This is slow... A real fifo would help 485 // Must be a fifo to go breadth-first 486 Hashtable<JavaHeapObject, JavaHeapObject> visited = new Hashtable<JavaHeapObject, JavaHeapObject>(); 487 // Objects are added here right after being added to fifo. 488 Vector<ReferenceChain> result = new Vector<ReferenceChain>(); 489 visited.put(target, target); 490 fifo.addElement(new ReferenceChain(target, null)); 491 492 while (fifo.size() > 0) { 493 ReferenceChain chain = fifo.elementAt(0); 494 fifo.removeElementAt(0); 495 JavaHeapObject curr = chain.getObj(); 496 if (curr.getRoot() != null) { 497 result.addElement(chain); 498 // Even though curr is in the rootset, we want to explore its 499 // referrers, because they might be more interesting. 500 } 501 Enumeration<JavaThing> referrers = curr.getReferrers(); 502 while (referrers.hasMoreElements()) { 503 JavaHeapObject t = (JavaHeapObject)referrers.nextElement(); 504 if (t != null && !visited.containsKey(t)) { 505 if (includeWeak || !t.refersOnlyWeaklyTo(this, curr)) { 506 visited.put(t, t); 507 fifo.addElement(new ReferenceChain(t, chain)); 508 } 509 } 510 } 511 } 512 513 ReferenceChain[] realResult = new ReferenceChain[result.size()]; 514 for (int i = 0; i < result.size(); i++) { 515 realResult[i] = result.elementAt(i); 516 } 517 return realResult; 518 } 519 520 public boolean getUnresolvedObjectsOK() { 521 return unresolvedObjectsOK; 522 } 523 524 public void setUnresolvedObjectsOK(boolean v) { 525 unresolvedObjectsOK = v; 526 } 527 528 public JavaClass getWeakReferenceClass() { 529 return weakReferenceClass; 530 } 531 532 public int getReferentFieldIndex() { 533 return referentFieldIndex; 534 } 535 536 public JavaThing getNullThing() { 537 return nullThing; 538 } 539 540 public void setReachableExcludes(ReachableExcludes e) { 541 reachableExcludes = e; 542 } 543 544 public ReachableExcludes getReachableExcludes() { 545 return reachableExcludes; 546 } 547 548 // package privates 549 void addReferenceFromRoot(Root r, JavaHeapObject obj) { 550 Root root = rootsMap.get(obj); 551 if (root == null) { 552 rootsMap.put(obj, r); 553 } else { 554 rootsMap.put(obj, root.mostInteresting(r)); 555 } 556 } 557 558 Root getRoot(JavaHeapObject obj) { 559 return rootsMap.get(obj); 560 } 561 562 JavaClass getJavaLangClass() { 563 return javaLangClass; 564 } 565 566 JavaClass getJavaLangString() { 567 return javaLangString; 568 } 569 570 JavaClass getJavaLangClassLoader() { 571 return javaLangClassLoader; 572 } 573 574 JavaClass getOtherArrayType() { 575 if (otherArrayType == null) { 576 synchronized(this) { 577 if (otherArrayType == null) { 578 addFakeClass(new JavaClass("[<other>", 0, 0, 0, 0, 579 EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 580 0)); 581 otherArrayType = findClass("[<other>"); 582 } 583 } 584 } 585 return otherArrayType; 586 } 587 588 JavaClass getArrayClass(String elementSignature) { 589 JavaClass clazz; 590 synchronized(classes) { 591 clazz = findClass("[" + elementSignature); 592 if (clazz == null) { 593 clazz = new JavaClass("[" + elementSignature, 0, 0, 0, 0, 594 EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0); 595 addFakeClass(clazz); 596 // This is needed because the JDK only creates Class structures 597 // for array element types, not the arrays themselves. For 598 // analysis, though, we need to pretend that there's a 599 // JavaClass for the array type, too. 600 } 601 } 602 return clazz; 603 } 604 605 ReadBuffer getReadBuffer() { 606 return readBuf; 607 } 608 609 void setNew(JavaHeapObject obj, boolean isNew) { 610 initNewObjects(); 611 if (isNew) { 612 newObjects.put(obj, Boolean.TRUE); 613 } 614 } 615 616 boolean isNew(JavaHeapObject obj) { 617 if (newObjects != null) { 618 return newObjects.get(obj) != null; 619 } else { 620 return false; 621 } 622 } 623 624 // Internals only below this point 625 private Number makeId(long id) { 626 if (identifierSize == 4) { 627 return (int)id; 628 } else { 629 return id; 630 } 631 } 632 633 private void putInClassesMap(JavaClass c) { 634 String name = c.getName(); 635 if (classes.containsKey(name)) { 636 // more than one class can have the same name 637 // if so, create a unique name by appending 638 // - and id string to it. 639 name += "-" + c.getIdString(); 640 } 641 classes.put(c.getName(), c); 642 } 643 644 private void addFakeClass(JavaClass c) { 645 putInClassesMap(c); 646 c.resolve(this); 647 } 648 649 private void addFakeClass(Number id, JavaClass c) { 650 fakeClasses.put(id, c); 651 addFakeClass(c); 652 } 653 654 private synchronized void initNewObjects() { 655 if (newObjects == null) { 656 synchronized (this) { 657 if (newObjects == null) { 658 newObjects = new HashMap<JavaHeapObject, Boolean>(); 659 } 660 } 661 } 662 } 663 664 private synchronized void initSiteTraces() { 665 if (siteTraces == null) { 666 synchronized (this) { 667 if (siteTraces == null) { 668 siteTraces = new HashMap<JavaHeapObject, StackTrace>(); 669 } 670 } 671 } 672 } 673 674 @Override 675 public void close() throws IOException { 676 readBuf.close(); 677 } 678 679 }