1 /* 2 * Copyright (c) 1997, 2008, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 27 /* 28 * The Original Code is HAT. The Initial Developer of the 29 * Original Code is Bill Foote, with contributions from others 30 * at JavaSoft/Sun. 31 */ 32 33 package com.sun.tools.hat.internal.model; 34 35 import java.lang.ref.SoftReference; 36 import java.util.*; 37 import com.sun.tools.hat.internal.parser.ReadBuffer; 38 import com.sun.tools.hat.internal.util.Misc; 39 40 /** 41 * 42 * @author Bill Foote 43 */ 44 45 /** 46 * Represents a snapshot of the Java objects in the VM at one instant. 47 * This is the top-level "model" object read out of a single .hprof or .bod 48 * file. 49 */ 50 51 public class Snapshot { 52 53 public static long SMALL_ID_MASK = 0x0FFFFFFFFL; 54 public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 55 56 private static final JavaField[] EMPTY_FIELD_ARRAY = new JavaField[0]; 57 private static final JavaStatic[] EMPTY_STATIC_ARRAY = new JavaStatic[0]; 58 59 // all heap objects 60 private Hashtable<Number, JavaHeapObject> heapObjects = 61 new Hashtable<Number, JavaHeapObject>(); 62 63 private Hashtable<Number, JavaClass> fakeClasses = 64 new Hashtable<Number, JavaClass>(); 65 66 // all Roots in this Snapshot 67 private Vector<Root> roots = new Vector<Root>(); 68 69 // name-to-class map 70 private Map<String, JavaClass> classes = 71 new TreeMap<String, JavaClass>(); 72 73 // new objects relative to a baseline - lazily initialized 74 private volatile Map<JavaHeapObject, Boolean> newObjects; 75 76 // allocation site traces for all objects - lazily initialized 77 private volatile Map<JavaHeapObject, StackTrace> siteTraces; 78 79 // object-to-Root map for all objects 80 private Map<JavaHeapObject, Root> rootsMap = 81 new HashMap<JavaHeapObject, Root>(); 82 83 // soft cache of finalizeable objects - lazily initialized 84 private SoftReference<Vector> finalizablesCache; 85 86 // represents null reference 87 private JavaThing nullThing; 88 89 // java.lang.ref.Reference class 90 private JavaClass weakReferenceClass; 91 // index of 'referent' field in java.lang.ref.Reference class 92 private int referentFieldIndex; 93 94 // java.lang.Class class 95 private JavaClass javaLangClass; 96 // java.lang.String class 97 private JavaClass javaLangString; 98 // java.lang.ClassLoader class 99 private JavaClass javaLangClassLoader; 100 101 // unknown "other" array class 102 private volatile JavaClass otherArrayType; 103 // Stuff to exclude from reachable query 104 private ReachableExcludes reachableExcludes; 105 // the underlying heap dump buffer 106 private ReadBuffer readBuf; 107 108 // True iff some heap objects have isNew set 109 private boolean hasNewSet; 110 private boolean unresolvedObjectsOK; 111 112 // whether object array instances have new style class or 113 // old style (element) class. 114 private boolean newStyleArrayClass; 115 116 // object id size in the heap dump 117 private int identifierSize = 4; 118 119 // minimum object size - accounts for object header in 120 // most Java virtual machines - we assume 2 identifierSize 121 // (which is true for Sun's hotspot JVM). 122 private int minimumObjectSize; 123 124 public Snapshot(ReadBuffer buf) { 125 nullThing = new HackJavaValue("<null>", 0); 126 readBuf = buf; 127 } 128 129 public void setSiteTrace(JavaHeapObject obj, StackTrace trace) { 130 if (trace != null && trace.getFrames().length != 0) { 131 initSiteTraces(); 132 siteTraces.put(obj, trace); 133 } 134 } 135 136 public StackTrace getSiteTrace(JavaHeapObject obj) { 137 if (siteTraces != null) { 138 return siteTraces.get(obj); 139 } else { 140 return null; 141 } 142 } 143 144 public void setNewStyleArrayClass(boolean value) { 145 newStyleArrayClass = value; 146 } 147 148 public boolean isNewStyleArrayClass() { 149 return newStyleArrayClass; 150 } 151 152 public void setIdentifierSize(int size) { 153 identifierSize = size; 154 minimumObjectSize = 2 * size; 155 } 156 157 public int getIdentifierSize() { 158 return identifierSize; 159 } 160 161 public int getMinimumObjectSize() { 162 return minimumObjectSize; 163 } 164 165 public void addHeapObject(long id, JavaHeapObject ho) { 166 heapObjects.put(makeId(id), ho); 167 } 168 169 public void addRoot(Root r) { 170 r.setIndex(roots.size()); 171 roots.addElement(r); 172 } 173 174 public void addClass(long id, JavaClass c) { 175 addHeapObject(id, c); 176 putInClassesMap(c); 177 } 178 179 JavaClass addFakeInstanceClass(long classID, int instSize) { 180 // Create a fake class name based on ID. 181 String name = "unknown-class<@" + Misc.toHex(classID) + ">"; 182 183 // Create fake fields convering the given instance size. 184 // Create as many as int type fields and for the left over 185 // size create byte type fields. 186 int numInts = instSize / 4; 187 int numBytes = instSize % 4; 188 JavaField[] fields = new JavaField[numInts + numBytes]; 189 int i; 190 for (i = 0; i < numInts; i++) { 191 fields[i] = new JavaField("unknown-field-" + i, "I"); 192 } 193 for (i = 0; i < numBytes; i++) { 194 fields[i + numInts] = new JavaField("unknown-field-" + 195 i + numInts, "B"); 196 } 197 198 // Create fake instance class 199 JavaClass c = new JavaClass(name, 0, 0, 0, 0, fields, 200 EMPTY_STATIC_ARRAY, instSize); 201 // Add the class 202 addFakeClass(makeId(classID), c); 203 return c; 204 } 205 206 207 /** 208 * @return true iff it's possible that some JavaThing instances might 209 * isNew set 210 * 211 * @see JavaThing.isNew() 212 */ 213 public boolean getHasNewSet() { 214 return hasNewSet; 215 } 216 217 // 218 // Used in the body of resolve() 219 // 220 private static class MyVisitor extends AbstractJavaHeapObjectVisitor { 221 JavaHeapObject t; 222 public void visit(JavaHeapObject other) { 223 other.addReferenceFrom(t); 224 } 225 } 226 227 // To show heap parsing progress, we print a '.' after this limit 228 private static final int DOT_LIMIT = 5000; 229 230 /** 231 * Called after reading complete, to initialize the structure 232 */ 233 public void resolve(boolean calculateRefs) { 234 System.out.println("Resolving " + heapObjects.size() + " objects..."); 235 236 // First, resolve the classes. All classes must be resolved before 237 // we try any objects, because the objects use classes in their 238 // resolution. 239 javaLangClass = findClass("java.lang.Class"); 240 if (javaLangClass == null) { 241 System.out.println("WARNING: hprof file does not include java.lang.Class!"); 242 javaLangClass = new JavaClass("java.lang.Class", 0, 0, 0, 0, 243 EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0); 244 addFakeClass(javaLangClass); 245 } 246 javaLangString = findClass("java.lang.String"); 247 if (javaLangString == null) { 248 System.out.println("WARNING: hprof file does not include java.lang.String!"); 249 javaLangString = new JavaClass("java.lang.String", 0, 0, 0, 0, 250 EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0); 251 addFakeClass(javaLangString); 252 } 253 javaLangClassLoader = findClass("java.lang.ClassLoader"); 254 if (javaLangClassLoader == null) { 255 System.out.println("WARNING: hprof file does not include java.lang.ClassLoader!"); 256 javaLangClassLoader = new JavaClass("java.lang.ClassLoader", 0, 0, 0, 0, 257 EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0); 258 addFakeClass(javaLangClassLoader); 259 } 260 261 for (JavaHeapObject t : heapObjects.values()) { 262 if (t instanceof JavaClass) { 263 t.resolve(this); 264 } 265 } 266 267 // Now, resolve everything else. 268 for (JavaHeapObject t : heapObjects.values()) { 269 if (!(t instanceof JavaClass)) { 270 t.resolve(this); 271 } 272 } 273 274 heapObjects.putAll(fakeClasses); 275 fakeClasses.clear(); 276 277 weakReferenceClass = findClass("java.lang.ref.Reference"); 278 if (weakReferenceClass == null) { // JDK 1.1.x 279 weakReferenceClass = findClass("sun.misc.Ref"); 280 referentFieldIndex = 0; 281 } else { 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.setupReferers(); 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 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 ReferenceChain[] 440 rootsetReferencesTo(JavaHeapObject target, boolean includeWeak) { 441 Vector<ReferenceChain> fifo = new Vector<ReferenceChain>(); // This is slow... A real fifo would help 442 // Must be a fifo to go breadth-first 443 Hashtable<JavaHeapObject, JavaHeapObject> visited = new Hashtable<JavaHeapObject, JavaHeapObject>(); 444 // Objects are added here right after being added to fifo. 445 Vector<ReferenceChain> result = new Vector<ReferenceChain>(); 446 visited.put(target, target); 447 fifo.addElement(new ReferenceChain(target, null)); 448 449 while (fifo.size() > 0) { 450 ReferenceChain chain = fifo.elementAt(0); 451 fifo.removeElementAt(0); 452 JavaHeapObject curr = chain.getObj(); 453 if (curr.getRoot() != null) { 454 result.addElement(chain); 455 // Even though curr is in the rootset, we want to explore its 456 // referers, because they might be more interesting. 457 } 458 Enumeration referers = curr.getReferers(); 459 while (referers.hasMoreElements()) { 460 JavaHeapObject t = (JavaHeapObject) referers.nextElement(); 461 if (t != null && !visited.containsKey(t)) { 462 if (includeWeak || !t.refersOnlyWeaklyTo(this, curr)) { 463 visited.put(t, t); 464 fifo.addElement(new ReferenceChain(t, chain)); 465 } 466 } 467 } 468 } 469 470 ReferenceChain[] realResult = new ReferenceChain[result.size()]; 471 for (int i = 0; i < result.size(); i++) { 472 realResult[i] = result.elementAt(i); 473 } 474 return realResult; 475 } 476 477 public boolean getUnresolvedObjectsOK() { 478 return unresolvedObjectsOK; 479 } 480 481 public void setUnresolvedObjectsOK(boolean v) { 482 unresolvedObjectsOK = v; 483 } 484 485 public JavaClass getWeakReferenceClass() { 486 return weakReferenceClass; 487 } 488 489 public int getReferentFieldIndex() { 490 return referentFieldIndex; 491 } 492 493 public JavaThing getNullThing() { 494 return nullThing; 495 } 496 497 public void setReachableExcludes(ReachableExcludes e) { 498 reachableExcludes = e; 499 } 500 501 public ReachableExcludes getReachableExcludes() { 502 return reachableExcludes; 503 } 504 505 // package privates 506 void addReferenceFromRoot(Root r, JavaHeapObject obj) { 507 Root root = rootsMap.get(obj); 508 if (root == null) { 509 rootsMap.put(obj, r); 510 } else { 511 rootsMap.put(obj, root.mostInteresting(r)); 512 } 513 } 514 515 Root getRoot(JavaHeapObject obj) { 516 return rootsMap.get(obj); 517 } 518 519 JavaClass getJavaLangClass() { 520 return javaLangClass; 521 } 522 523 JavaClass getJavaLangString() { 524 return javaLangString; 525 } 526 527 JavaClass getJavaLangClassLoader() { 528 return javaLangClassLoader; 529 } 530 531 JavaClass getOtherArrayType() { 532 if (otherArrayType == null) { 533 synchronized(this) { 534 if (otherArrayType == null) { 535 addFakeClass(new JavaClass("[<other>", 0, 0, 0, 0, 536 EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 537 0)); 538 otherArrayType = findClass("[<other>"); 539 } 540 } 541 } 542 return otherArrayType; 543 } 544 545 JavaClass getArrayClass(String elementSignature) { 546 JavaClass clazz; 547 synchronized(classes) { 548 clazz = findClass("[" + elementSignature); 549 if (clazz == null) { 550 clazz = new JavaClass("[" + elementSignature, 0, 0, 0, 0, 551 EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0); 552 addFakeClass(clazz); 553 // This is needed because the JDK only creates Class structures 554 // for array element types, not the arrays themselves. For 555 // analysis, though, we need to pretend that there's a 556 // JavaClass for the array type, too. 557 } 558 } 559 return clazz; 560 } 561 562 ReadBuffer getReadBuffer() { 563 return readBuf; 564 } 565 566 void setNew(JavaHeapObject obj, boolean isNew) { 567 initNewObjects(); 568 if (isNew) { 569 newObjects.put(obj, Boolean.TRUE); 570 } 571 } 572 573 boolean isNew(JavaHeapObject obj) { 574 if (newObjects != null) { 575 return newObjects.get(obj) != null; 576 } else { 577 return false; 578 } 579 } 580 581 // Internals only below this point 582 private Number makeId(long id) { 583 if (identifierSize == 4) { 584 return new Integer((int)id); 585 } else { 586 return id; 587 } 588 } 589 590 private void putInClassesMap(JavaClass c) { 591 String name = c.getName(); 592 if (classes.containsKey(name)) { 593 // more than one class can have the same name 594 // if so, create a unique name by appending 595 // - and id string to it. 596 name += "-" + c.getIdString(); 597 } 598 classes.put(c.getName(), c); 599 } 600 601 private void addFakeClass(JavaClass c) { 602 putInClassesMap(c); 603 c.resolve(this); 604 } 605 606 private void addFakeClass(Number id, JavaClass c) { 607 fakeClasses.put(id, c); 608 addFakeClass(c); 609 } 610 611 private synchronized void initNewObjects() { 612 if (newObjects == null) { 613 synchronized (this) { 614 if (newObjects == null) { 615 newObjects = new HashMap<JavaHeapObject, Boolean>(); 616 } 617 } 618 } 619 } 620 621 private synchronized void initSiteTraces() { 622 if (siteTraces == null) { 623 synchronized (this) { 624 if (siteTraces == null) { 625 siteTraces = new HashMap<JavaHeapObject, StackTrace>(); 626 } 627 } 628 } 629 } 630 }