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