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