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