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