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