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 }