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 // 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 213 /** 214 * @return true iff it's possible that some JavaThing instances might 215 * isNew set 216 * 217 * @see JavaThing.isNew() 218 */ 219 public boolean getHasNewSet() { 220 return hasNewSet; 221 } 222 223 // 224 // Used in the body of resolve() 225 // 226 private static class MyVisitor extends AbstractJavaHeapObjectVisitor { 227 JavaHeapObject t; 228 public void visit(JavaHeapObject other) { 229 other.addReferenceFrom(t); 230 } 231 } 232 233 // To show heap parsing progress, we print a '.' after this limit 234 private static final int DOT_LIMIT = 5000; 235 236 /** 237 * Called after reading complete, to initialize the structure 238 */ 239 public void resolve(boolean calculateRefs) { 240 System.out.println("Resolving " + heapObjects.size() + " objects..."); 241 242 // First, resolve the classes. All classes must be resolved before 243 // we try any objects, because the objects use classes in their 244 // resolution. 245 javaLangClass = findClass("java.lang.Class"); 246 if (javaLangClass == null) { 247 System.out.println("WARNING: hprof file does not include java.lang.Class!"); 248 javaLangClass = new JavaClass("java.lang.Class", 0, 0, 0, 0, 249 EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0); 250 addFakeClass(javaLangClass); 251 } 252 javaLangString = findClass("java.lang.String"); 253 if (javaLangString == null) { 254 System.out.println("WARNING: hprof file does not include java.lang.String!"); 255 javaLangString = new JavaClass("java.lang.String", 0, 0, 0, 0, 256 EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0); 257 addFakeClass(javaLangString); 258 } 259 javaLangClassLoader = findClass("java.lang.ClassLoader"); 260 if (javaLangClassLoader == null) { 261 System.out.println("WARNING: hprof file does not include java.lang.ClassLoader!"); 262 javaLangClassLoader = new JavaClass("java.lang.ClassLoader", 0, 0, 0, 0, 263 EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0); 264 addFakeClass(javaLangClassLoader); 265 } 266 267 for (JavaHeapObject t : heapObjects.values()) { 268 if (t instanceof JavaClass) { 269 t.resolve(this); 270 } 271 } 272 273 // Now, resolve everything else. 274 for (JavaHeapObject t : heapObjects.values()) { 275 if (!(t instanceof JavaClass)) { 276 t.resolve(this); 277 } 278 } 279 280 heapObjects.putAll(fakeClasses); 281 fakeClasses.clear(); 282 283 weakReferenceClass = findClass("java.lang.ref.Reference"); 284 referentFieldIndex = 0; 285 if (weakReferenceClass != null) { 286 JavaField[] fields = weakReferenceClass.getFieldsForInstance(); 287 for (int i = 0; i < fields.length; i++) { 288 if ("referent".equals(fields[i].getName())) { 289 referentFieldIndex = i; 290 break; 291 } 292 } 293 } 294 295 if (calculateRefs) { 296 calculateReferencesToObjects(); 297 System.out.print("Eliminating duplicate references"); 298 System.out.flush(); 299 // This println refers to the *next* step 300 } 301 int count = 0; 302 for (JavaHeapObject t : heapObjects.values()) { 303 t.setupReferrers(); 304 ++count; 305 if (calculateRefs && count % DOT_LIMIT == 0) { 306 System.out.print("."); 307 System.out.flush(); 308 } 309 } 310 if (calculateRefs) { 311 System.out.println(""); 312 } 313 314 // to ensure that Iterator.remove() on getClasses() 315 // result will throw exception.. 316 classes = Collections.unmodifiableMap(classes); 317 } 318 319 private void calculateReferencesToObjects() { 320 System.out.print("Chasing references, expect " 321 + (heapObjects.size() / DOT_LIMIT) + " dots"); 322 System.out.flush(); 323 int count = 0; 324 MyVisitor visitor = new MyVisitor(); 325 for (JavaHeapObject t : heapObjects.values()) { 326 visitor.t = t; 327 // call addReferenceFrom(t) on all objects t references: 328 t.visitReferencedObjects(visitor); 329 ++count; 330 if (count % DOT_LIMIT == 0) { 331 System.out.print("."); 332 System.out.flush(); 333 } 334 } 335 System.out.println(); 336 for (Root r : roots) { 337 r.resolve(this); 338 JavaHeapObject t = findThing(r.getId()); 339 if (t != null) { 340 t.addReferenceFromRoot(r); 341 } 342 } 343 } 344 345 public void markNewRelativeTo(Snapshot baseline) { 346 hasNewSet = true; 347 for (JavaHeapObject t : heapObjects.values()) { 348 boolean isNew; 349 long thingID = t.getId(); 350 if (thingID == 0L || thingID == -1L) { 351 isNew = false; 352 } else { 353 JavaThing other = baseline.findThing(t.getId()); 354 if (other == null) { 355 isNew = true; 356 } else { 357 isNew = !t.isSameTypeAs(other); 358 } 359 } 360 t.setNew(isNew); 361 } 362 } 363 364 public Enumeration<JavaHeapObject> getThings() { 365 return heapObjects.elements(); 366 } 367 368 369 public JavaHeapObject findThing(long id) { 370 Number idObj = makeId(id); 371 JavaHeapObject jho = heapObjects.get(idObj); 372 return jho != null? jho : fakeClasses.get(idObj); 373 } 374 375 public JavaHeapObject findThing(String id) { 376 return findThing(Misc.parseHex(id)); 377 } 378 379 public JavaClass findClass(String name) { 380 if (name.startsWith("0x")) { 381 return (JavaClass) findThing(name); 382 } else { 383 return classes.get(name); 384 } 385 } 386 387 /** 388 * Return an Iterator of all of the classes in this snapshot. 389 **/ 390 public Iterator<JavaClass> getClasses() { 391 // note that because classes is a TreeMap 392 // classes are already sorted by name 393 return classes.values().iterator(); 394 } 395 396 public JavaClass[] getClassesArray() { 397 JavaClass[] res = new JavaClass[classes.size()]; 398 classes.values().toArray(res); 399 return res; 400 } 401 402 public synchronized Enumeration<?> getFinalizerObjects() { 403 Vector<?> obj; 404 if (finalizablesCache != null && 405 (obj = finalizablesCache.get()) != null) { 406 return obj.elements(); 407 } 408 409 JavaClass clazz = findClass("java.lang.ref.Finalizer"); 410 JavaObject queue = (JavaObject) clazz.getStaticField("queue"); 411 JavaThing tmp = queue.getField("head"); 412 Vector<JavaHeapObject> finalizables = new Vector<JavaHeapObject>(); 413 if (tmp != getNullThing()) { 414 JavaObject head = (JavaObject) tmp; 415 while (true) { 416 JavaHeapObject referent = (JavaHeapObject) head.getField("referent"); 417 JavaThing next = head.getField("next"); 418 if (next == getNullThing() || next.equals(head)) { 419 break; 420 } 421 head = (JavaObject) next; 422 finalizables.add(referent); 423 } 424 } 425 finalizablesCache = new SoftReference<Vector<?>>(finalizables); 426 return finalizables.elements(); 427 } 428 429 public Enumeration<Root> getRoots() { 430 return roots.elements(); 431 } 432 433 public Root[] getRootsArray() { 434 Root[] res = new Root[roots.size()]; 435 roots.toArray(res); 436 return res; 437 } 438 439 public Root getRootAt(int i) { 440 return roots.elementAt(i); 441 } 442 443 public List<ThreadObject> getThreads() { 444 return Collections.unmodifiableList(threads); 445 } 446 447 public ReferenceChain[] 448 rootsetReferencesTo(JavaHeapObject target, boolean includeWeak) { 449 Vector<ReferenceChain> fifo = new Vector<ReferenceChain>(); // This is slow... A real fifo would help 450 // Must be a fifo to go breadth-first 451 Hashtable<JavaHeapObject, JavaHeapObject> visited = new Hashtable<JavaHeapObject, JavaHeapObject>(); 452 // Objects are added here right after being added to fifo. 453 Vector<ReferenceChain> result = new Vector<ReferenceChain>(); 454 visited.put(target, target); 455 fifo.addElement(new ReferenceChain(target, null)); 456 457 while (fifo.size() > 0) { 458 ReferenceChain chain = fifo.elementAt(0); 459 fifo.removeElementAt(0); 460 JavaHeapObject curr = chain.getObj(); 461 if (curr.getRoot() != null) { 462 result.addElement(chain); 463 // Even though curr is in the rootset, we want to explore its 464 // referrers, because they might be more interesting. 465 } 466 Enumeration<JavaThing> referrers = curr.getReferrers(); 467 while (referrers.hasMoreElements()) { 468 JavaHeapObject t = (JavaHeapObject)referrers.nextElement(); 469 if (t != null && !visited.containsKey(t)) { 470 if (includeWeak || !t.refersOnlyWeaklyTo(this, curr)) { 471 visited.put(t, t); 472 fifo.addElement(new ReferenceChain(t, chain)); 473 } 474 } 475 } 476 } 477 478 ReferenceChain[] realResult = new ReferenceChain[result.size()]; 479 for (int i = 0; i < result.size(); i++) { 480 realResult[i] = result.elementAt(i); 481 } 482 return realResult; 483 } 484 485 public boolean getUnresolvedObjectsOK() { 486 return unresolvedObjectsOK; 487 } 488 489 public void setUnresolvedObjectsOK(boolean v) { 490 unresolvedObjectsOK = v; 491 } 492 493 public JavaClass getWeakReferenceClass() { 494 return weakReferenceClass; 495 } 496 497 public int getReferentFieldIndex() { 498 return referentFieldIndex; 499 } 500 501 public JavaThing getNullThing() { 502 return nullThing; 503 } 504 505 public void setReachableExcludes(ReachableExcludes e) { 506 reachableExcludes = e; 507 } 508 509 public ReachableExcludes getReachableExcludes() { 510 return reachableExcludes; 511 } 512 513 // package privates 514 void addReferenceFromRoot(Root r, JavaHeapObject obj) { 515 Root root = rootsMap.get(obj); 516 if (root == null) { 517 rootsMap.put(obj, r); 518 } else { 519 rootsMap.put(obj, root.mostInteresting(r)); 520 } 521 } 522 523 Root getRoot(JavaHeapObject obj) { 524 return rootsMap.get(obj); 525 } 526 527 JavaClass getJavaLangClass() { 528 return javaLangClass; 529 } 530 531 JavaClass getJavaLangString() { 532 return javaLangString; 533 } 534 535 JavaClass getJavaLangClassLoader() { 536 return javaLangClassLoader; 537 } 538 539 JavaClass getOtherArrayType() { 540 if (otherArrayType == null) { 541 synchronized(this) { 542 if (otherArrayType == null) { 543 addFakeClass(new JavaClass("[<other>", 0, 0, 0, 0, 544 EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 545 0)); 546 otherArrayType = findClass("[<other>"); 547 } 548 } 549 } 550 return otherArrayType; 551 } 552 553 JavaClass getArrayClass(String elementSignature) { 554 JavaClass clazz; 555 synchronized(classes) { 556 clazz = findClass("[" + elementSignature); 557 if (clazz == null) { 558 clazz = new JavaClass("[" + elementSignature, 0, 0, 0, 0, 559 EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0); 560 addFakeClass(clazz); 561 // This is needed because the JDK only creates Class structures 562 // for array element types, not the arrays themselves. For 563 // analysis, though, we need to pretend that there's a 564 // JavaClass for the array type, too. 565 } 566 } 567 return clazz; 568 } 569 570 ReadBuffer getReadBuffer() { 571 return readBuf; 572 } 573 574 void setNew(JavaHeapObject obj, boolean isNew) { 575 initNewObjects(); 576 if (isNew) { 577 newObjects.put(obj, Boolean.TRUE); 578 } 579 } 580 581 boolean isNew(JavaHeapObject obj) { 582 if (newObjects != null) { 583 return newObjects.get(obj) != null; 584 } else { 585 return false; 586 } 587 } 588 589 // Internals only below this point 590 private Number makeId(long id) { 591 if (identifierSize == 4) { 592 return (int)id; 593 } else { 594 return id; 595 } 596 } 597 598 private void putInClassesMap(JavaClass c) { 599 String name = c.getName(); 600 if (classes.containsKey(name)) { 601 // more than one class can have the same name 602 // if so, create a unique name by appending 603 // - and id string to it. 604 name += "-" + c.getIdString(); 605 } 606 classes.put(c.getName(), c); 607 } 608 609 private void addFakeClass(JavaClass c) { 610 putInClassesMap(c); 611 c.resolve(this); 612 } 613 614 private void addFakeClass(Number id, JavaClass c) { 615 fakeClasses.put(id, c); 616 addFakeClass(c); 617 } 618 619 private synchronized void initNewObjects() { 620 if (newObjects == null) { 621 synchronized (this) { 622 if (newObjects == null) { 623 newObjects = new HashMap<JavaHeapObject, Boolean>(); 624 } 625 } 626 } 627 } 628 629 private synchronized void initSiteTraces() { 630 if (siteTraces == null) { 631 synchronized (this) { 632 if (siteTraces == null) { 633 siteTraces = new HashMap<JavaHeapObject, StackTrace>(); 634 } 635 } 636 } 637 } 638 639 @Override 640 public void close() throws Exception { 641 readBuf.close(); 642 } 643 644 }