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 }