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