1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ahat.heapdump;
18 
19 import com.android.ahat.progress.Progress;
20 import java.awt.image.BufferedImage;
21 import java.util.ArrayDeque;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.Deque;
26 import java.util.EnumMap;
27 import java.util.List;
28 import java.util.Queue;
29 
30 /**
31  * A Java instance from a parsed heap dump. It is the base class used for all
32  * kinds of Java instances, including normal Java objects, class objects, and
33  * arrays.
34  */
35 public abstract class AhatInstance implements Diffable<AhatInstance> {
36   // The id of this instance from the heap dump.
37   private final long mId;
38 
39   // Fields initialized in initialize().
40   private AhatHeap mHeap;
41   private AhatClassObj mClassObj;
42   private Site mSite;
43 
44   // Bit vector of the root types of this object.
45   private int mRootTypes;
46 
47   // Field initialized via addRegisterednativeSize.
48   private long mRegisteredNativeSize = 0;
49 
50   // Fields initialized in computeReachability().
51   private Reachability mReachability = Reachability.UNREACHABLE;
52   private AhatInstance mNextInstanceToGcRoot;
53   private String mNextInstanceToGcRootField;
54   private ArrayList<AhatInstance> mReverseReferences;
55 
56   // Fields initialized in DominatorsComputation.computeDominators().
57   // mDominated - the list of instances immediately dominated by this instance.
58   // mRetainedSizes - retained size indexed by heap index.
59   private AhatInstance mImmediateDominator;
60   private List<AhatInstance> mDominated = new ArrayList<AhatInstance>();
61   private Size[] mRetainedSizes;
62 
63   // The baseline instance for purposes of diff.
64   private AhatInstance mBaseline;
65 
66   // temporary user data associated with this instance. This is used for a
67   // couple different purposes:
68   // 1. During parsing of instances, to store temporary field data.
69   // 2. During dominators computation, to store the dominators computation state.
70   private Object mTemporaryUserData;
71 
AhatInstance(long id)72   AhatInstance(long id) {
73     mId = id;
74     mBaseline = this;
75   }
76 
77   /**
78    * Initialize this AhatInstance based on the the given info.
79    */
initialize(AhatHeap heap, Site site, AhatClassObj classObj)80   void initialize(AhatHeap heap, Site site, AhatClassObj classObj) {
81     mHeap = heap;
82     mSite = site;
83     mClassObj = classObj;
84   }
85 
86   /**
87    * Returns a unique identifier for this instance.
88    *
89    * @return id of the instance
90    */
getId()91   public long getId() {
92     return mId;
93   }
94 
95   /**
96    * Returns the number of bytes used for this object in the heap.
97    * The returned size is a shallow size for the object that does not include
98    * sizes of other objects dominated by this object.
99    *
100    * @return the shallow size of the object
101    */
getSize()102   public Size getSize() {
103     return new Size(mClassObj.getInstanceSize() + getExtraJavaSize(), mRegisteredNativeSize);
104   }
105 
106   /**
107    * Returns the number of bytes taken up by this object on the Java heap
108    * beyond the standard instance size as recorded by the class of this
109    * instance.
110    *
111    * For example, class objects will have extra size for static fields and
112    * array objects will have extra size for the array elements.
113    */
getExtraJavaSize()114   abstract long getExtraJavaSize();
115 
116   /**
117    * Returns the number of bytes retained by this object in the given heap.
118    * The returned size includes the shallow size of this object and the size
119    * of all objects directly or indirectly retained by this object. Only those
120    * objects allocated on the given heap are included in the reported size.
121    *
122    * @param heap the heap to get the retained size for
123    * @return the retained size of the object
124    */
getRetainedSize(AhatHeap heap)125   public Size getRetainedSize(AhatHeap heap) {
126     int index = heap.getIndex();
127     if (mRetainedSizes != null && 0 <= index && index < mRetainedSizes.length) {
128       return mRetainedSizes[heap.getIndex()];
129     }
130     return Size.ZERO;
131   }
132 
133   /**
134    * Returns the total number of bytes retained by this object. The returned
135    * size includes the shallow size of this object and the size of all objects
136    * directly or indirectly retained by this object.
137    *
138    * @return the total retained size of the object
139    */
getTotalRetainedSize()140   public Size getTotalRetainedSize() {
141     Size size = Size.ZERO;
142     if (mRetainedSizes != null) {
143       for (int i = 0; i < mRetainedSizes.length; i++) {
144         size = size.plus(mRetainedSizes[i]);
145       }
146     }
147     return size;
148   }
149 
150   /**
151    * Increment the number of registered native bytes tied to this object.
152    */
addRegisteredNativeSize(long size)153   void addRegisteredNativeSize(long size) {
154     mRegisteredNativeSize += size;
155   }
156 
157   /**
158    * Returns the reachability of the instance.
159    *
160    * @return the reachability of the instance.
161    */
getReachability()162   public Reachability getReachability() {
163     return mReachability;
164   }
165 
166   /**
167    * Returns true if this object is strongly reachable. An object is strongly
168    * reachable if there exists a path of (strong) references from some root
169    * object to this object.
170    *
171    * @return true if the object is strongly reachable
172    */
isStronglyReachable()173   public boolean isStronglyReachable() {
174     return mReachability == Reachability.STRONG;
175   }
176 
177   /**
178    * Returns true if this object is reachable only through a
179    * soft/weak/phantom/finalizer reference. An object is weakly reachable if
180    * it is not strongly reachable but there still exists a path of references
181    * from some root object to this object.  Because the object is not strongly
182    * reachable, any such path must contain a SoftReference, WeakReference,
183    * PhantomReference, or FinalizerReference somewhere along it.
184    * <p>
185    * Unlike a strongly reachable object, a weakly reachable object is allowed
186    * to be garbage collected.
187    *
188    * @deprecated Use {@link #getReachability()} instead, which can distinguish
189    *             among soft, weak, phantom, and other kinds of references.
190    *
191    * @return true if the object is weakly reachable
192    */
isWeaklyReachable()193   @Deprecated public boolean isWeaklyReachable() {
194     return !isStronglyReachable() && !isUnreachable();
195   }
196 
197   /**
198    * Returns true if this object is completely unreachable. An object is
199    * completely unreachable if there is no path to the object from some root
200    * object, neither through strong nor soft/weak/phantom/finalizer
201    * references.
202    *
203    * @return true if the object is completely unreachable
204    */
isUnreachable()205   public boolean isUnreachable() {
206     return mReachability == Reachability.UNREACHABLE;
207   }
208 
209   /**
210    * Returns the heap that this instance is allocated on.
211    *
212    * @return heap the instance is allocated on
213    */
getHeap()214   public AhatHeap getHeap() {
215     return mHeap;
216   }
217 
218   /**
219    * Returns an iterator over the references this AhatInstance has to other
220    * AhatInstances.
221    */
getReferences()222   abstract Iterable<Reference> getReferences();
223 
224   /**
225    * Returns true if this instance is a GC root.
226    *
227    * @return true if this instance is a GC root.
228    */
isRoot()229   public boolean isRoot() {
230     return mRootTypes != 0;
231   }
232 
233   /**
234    * Marks this instance as being a root of the given type.
235    */
addRootType(RootType type)236   void addRootType(RootType type) {
237     mRootTypes |= type.mask;
238   }
239 
240   /**
241    * Returns a list of the root types of this object.
242    * Returns null if this object is not a root.
243    *
244    * @return list of the objects root types
245    */
getRootTypes()246   public Collection<RootType> getRootTypes() {
247     if (!isRoot()) {
248       return null;
249     }
250 
251     List<RootType> types = new ArrayList<RootType>();
252     for (RootType type : RootType.values()) {
253       if ((mRootTypes & type.mask) != 0) {
254         types.add(type);
255       }
256     }
257     return types;
258   }
259 
260   /**
261    * Returns the immediate dominator of this instance.
262    * Returns null if this is a root instance.
263    *
264    * @return the immediate dominator of this instance
265    */
getImmediateDominator()266   public AhatInstance getImmediateDominator() {
267     return mImmediateDominator;
268   }
269 
270   /**
271    * Returns a list of objects immediately dominated by this instance.
272    *
273    * @return list of immediately dominated objects
274    */
getDominated()275   public List<AhatInstance> getDominated() {
276     return mDominated;
277   }
278 
279   /**
280    * Returns the site where this instance was allocated.
281    *
282    * @return the object's allocation site
283    */
getSite()284   public Site getSite() {
285     return mSite;
286   }
287 
288   /**
289    * Returns true if this instance is a class object
290    *
291    * @return true if this instance is a class object
292    */
isClassObj()293   public boolean isClassObj() {
294     // Overridden by AhatClassObj.
295     return false;
296   }
297 
298   /**
299    * Returns this as an AhatClassObj if this is an AhatClassObj.
300    * Returns null if this is not an AhatClassObj.
301    *
302    * @return this instance as a class object
303    */
asClassObj()304   public AhatClassObj asClassObj() {
305     // Overridden by AhatClassObj.
306     return null;
307   }
308 
309   /**
310    * Returns the class object for this instance.
311    * For example, if this object is an instance of java.lang.String, this
312    * method returns the AhatClassObj for java.lang.String.
313    *
314    * @return the instance's class object
315    */
getClassObj()316   public AhatClassObj getClassObj() {
317     return mClassObj;
318   }
319 
320   /**
321    * Returns the name of the class this object belongs to.
322    * For example, if this object is an instance of java.lang.String, returns
323    * "java.lang.String".
324    *
325    * @return the name of this instance's class
326    */
getClassName()327   public String getClassName() {
328     AhatClassObj classObj = getClassObj();
329     return classObj == null ? "???" : classObj.getName();
330   }
331 
332   /**
333    * Returns true if this is an instance of a (subclass of a) class with the
334    * given name.
335    *
336    * @param className the name of the class to check for
337    * @return true if this is an instance of a (subclass of a) class with the
338    *              given name
339    */
isInstanceOfClass(String className)340   public boolean isInstanceOfClass(String className) {
341     AhatClassObj cls = getClassObj();
342     while (cls != null) {
343       if (className.equals(cls.getName())) {
344         return true;
345       }
346       cls = cls.getSuperClassObj();
347     }
348     return false;
349   }
350 
351   /**
352    * Returns true if the given instance is an array instance.
353    *
354    * @return true if the given instance is an array instance
355    */
isArrayInstance()356   public boolean isArrayInstance() {
357     // Overridden by AhatArrayInstance.
358     return false;
359   }
360 
361   /**
362    * Returns this as an AhatArrayInstance if this is an AhatArrayInstance.
363    * Returns null if this is not an AhatArrayInstance.
364    *
365    * @return this instance as an array instance
366    */
asArrayInstance()367   public AhatArrayInstance asArrayInstance() {
368     // Overridden by AhatArrayInstance.
369     return null;
370   }
371 
372   /**
373    * Returns true if this instance is a class instance.
374    *
375    * @return true if this instance is a class instance
376    */
isClassInstance()377   public boolean isClassInstance() {
378     return false;
379   }
380 
381   /**
382    * Returns this as an AhatClassInstance if this is an AhatClassInstance.
383    * Returns null if this is not an AhatClassInstance.
384    *
385    * @return this instance as a class instance
386    */
asClassInstance()387   public AhatClassInstance asClassInstance() {
388     return null;
389   }
390 
391   /**
392    * Returns the <code>referent</code> associated with this instance.
393    * This is only relevant for instances of java.lang.ref.Reference or its
394    * subclasses. Returns null if the instance has no referent associated with
395    * it.
396    *
397    * @return the referent associated with this instance
398    */
getReferent()399   public AhatInstance getReferent() {
400     // Overridden by AhatClassInstance.
401     return null;
402   }
403 
404   /**
405    * Returns a list of objects with any kind of reference to this object.
406    *
407    * @return the objects referencing this object
408    */
getReverseReferences()409   public List<AhatInstance> getReverseReferences() {
410     if (mReverseReferences != null) {
411       return mReverseReferences;
412     }
413     return Collections.emptyList();
414   }
415 
416   /**
417    * Returns a list of objects with (strong) references to this object.
418    *
419    * @deprecated Use {@link #getReverseReferences()} instead.
420    *
421    * @return the objects referencing this object
422    */
getHardReverseReferences()423   @Deprecated public List<AhatInstance> getHardReverseReferences() {
424     List<AhatInstance> refs = new ArrayList<AhatInstance>();
425     for (AhatInstance ref : getReverseReferences()) {
426       if (ref.getReachability() == Reachability.STRONG && ref.getReferent() != this) {
427         refs.add(ref);
428       }
429     }
430     return refs;
431   }
432 
433   /**
434    * Returns a list of objects with soft/weak/phantom/finalizer references to
435    * this object.
436    *
437    * @deprecated Use {@link #getReverseReferences()} instead.
438    *
439    * @return the objects weakly referencing this object
440    */
getSoftReverseReferences()441   @Deprecated public List<AhatInstance> getSoftReverseReferences() {
442     List<AhatInstance> refs = new ArrayList<AhatInstance>();
443     for (AhatInstance ref : getReverseReferences()) {
444       if (ref.getReachability() != Reachability.STRONG || ref.getReferent() == this) {
445         refs.add(ref);
446       }
447     }
448     return refs;
449   }
450 
451   /**
452    * Returns the value of a field of this instance. Returns null if the field
453    * value is null, the field couldn't be read, or there are multiple fields
454    * with the same name.
455    *
456    * @param fieldName the name of the field to get the value of
457    * @return the field value
458    */
getField(String fieldName)459   public Value getField(String fieldName) {
460     // Overridden by AhatClassInstance.
461     return null;
462   }
463 
464   /**
465    * Reads a reference field of this instance. Returns null if the field value
466    * is null, of primitive type, or if the field couldn't be read. There is no
467    * way using this method to distinguish between a reference field with value
468    * <code>null</code> and an invalid field.
469    *
470    * @param fieldName the name of the reference field to get the value of
471    * @return the reference field value
472    */
getRefField(String fieldName)473   public AhatInstance getRefField(String fieldName) {
474     // Overridden by AhatClassInstance.
475     return null;
476   }
477 
478   /**
479    * Returns the dex location associated with this object. Only applies to
480    * instances of dalvik.system.DexCache. If this is an instance of DexCache,
481    * returns the dex location for that dex cache. Otherwise returns null.
482    * If maxChars is non-negative, the returned location is truncated to
483    * maxChars in length.
484    *
485    * @param maxChars the maximum length of the returned string
486    * @return the dex location associated with this object
487    */
getDexCacheLocation(int maxChars)488   public String getDexCacheLocation(int maxChars) {
489     return null;
490   }
491 
492   /**
493    * Returns the name of the Binder proxy interface associated with this object.
494    * Only applies to instances of android.os.BinderProxy. If this is an
495    * instance of BinderProxy, returns the fully qualified binder interface name,
496    * otherwise returns null.
497    *
498    * @return the name of the binder interface associated with this object
499    */
getBinderProxyInterfaceName()500   public String getBinderProxyInterfaceName() {
501     return null;
502   }
503 
504   /**
505    * Returns the descriptor of the Binder token associated with this object.
506    * Only applies to instances of android.os.Binder. If this is an instance of
507    * android.os.Binder with a subclass of the name "descriptor$Stub", the
508    * object in question is a binder stub, and this function will return null.
509    * In that case, @see AhatInstance#getBinderStubInterfaceName
510    *
511    * @return the descriptor of this object, if it's a binder token
512    */
getBinderTokenDescriptor()513   public String getBinderTokenDescriptor() {
514     return null;
515   }
516 
517   /**
518    * Returns the name of the Binder stub interface associated with this object.
519    * Only applies to instances which are a subclass of android.os.Binder,
520    * and are an instance of class 'descriptor$Stub', where descriptor
521    * is the descriptor of the android.os.Binder object.
522    *
523    * @return the name of the binder interface associated with this object,
524    *         or null if this is not a binder stub interface.
525    */
getBinderStubInterfaceName()526   public String getBinderStubInterfaceName() {
527     return null;
528   }
529 
530   /**
531    * Returns the android.graphics.Bitmap instance associated with this object.
532    * Instances of android.graphics.Bitmap return themselves. If this is a
533    * byte[] array containing pixel data for an instance of
534    * android.graphics.Bitmap, that instance of android.graphics.Bitmap is
535    * returned. Otherwise null is returned.
536    *
537    * @return the bitmap instance associated with this object
538    */
getAssociatedBitmapInstance()539   public AhatInstance getAssociatedBitmapInstance() {
540     return null;
541   }
542 
543   /**
544    * Returns the class object that this object represents the overhead for.
545    * ART adds a fake byte[] $classOverhead static field to classes to show the
546    * overheads associated with the class. If this is one such byte[] instance,
547    * returns the class it is associated with. Otherwise null is returned.
548    *
549    * @return the class instance that this is the overhead for
550    */
getAssociatedClassForOverhead()551   public AhatClassObj getAssociatedClassForOverhead() {
552     return null;
553   }
554 
555   /**
556    * Returns the (bounded-length) string associated with this instance.
557    * Applies to instances of java.lang.String, char[], and in some cases
558    * byte[]. Returns null if this object cannot be interpreted as a string.
559    * If maxChars is non-negative, the returned string is truncated to maxChars
560    * characters in length.
561    *
562    * @param maxChars the maximum length of the returned string
563    * @return the string associated with this instance
564    */
asString(int maxChars)565   public String asString(int maxChars) {
566     // By default instances can't be interpreted as a string. This method is
567     // overridden by AhatClassInstance and AhatArrayInstance for those cases
568     // when an instance can be interpreted as a string.
569     return null;
570   }
571 
572   /**
573    * Returns the string associated with this instance. Applies to instances of
574    * java.lang.String, char[], and in some cases byte[]. Returns null if this
575    * object cannot be interpreted as a string.
576    *
577    * @return the string associated with this instance
578    */
asString()579   public String asString() {
580     return asString(-1);
581   }
582 
583   /**
584    * Returns the bitmap pixel data associated with this instance.
585    * This is relevant for instances of android.graphics.Bitmap and byte[].
586    * Returns null if there is no bitmap pixel data associated with the given
587    * instance.
588    *
589    * @return the bitmap pixel data associated with this image
590    */
asBitmap()591   public BufferedImage asBitmap() {
592     return null;
593   }
594 
595   static class RegisteredNativeAllocation {
596     public AhatInstance referent;
597     public long size;
598   };
599 
600   /**
601    * Return the registered native allocation that this instance represents, if
602    * any. This is relevant for instances of sun.misc.Cleaner.
603    */
asRegisteredNativeAllocation()604   RegisteredNativeAllocation asRegisteredNativeAllocation() {
605     return null;
606   }
607 
608   /**
609    * Returns a sample path from a GC root to this instance. The first element
610    * of the returned path is a GC root object. This instance is included as
611    * the last element of the path with an empty field description.
612    * <p>
613    * If the instance is strongly reachable, a path of string references will
614    * be returned. If the instance is weakly reachable, the returned path will
615    * include a soft/weak/phantom/finalizer reference somewhere along it.
616    * Returns null if this instance is not reachable.
617    *
618    * @return sample path from a GC root to this instance
619    * @see PathElement
620    */
getPathFromGcRoot()621   public List<PathElement> getPathFromGcRoot() {
622     if (isUnreachable()) {
623       return null;
624     }
625 
626     List<PathElement> path = new ArrayList<PathElement>();
627 
628     AhatInstance dom = this;
629     for (PathElement elem = new PathElement(this, ""); elem != null;
630         elem = getNextPathElementToGcRoot(elem.instance)) {
631       if (elem.instance.equals(dom)) {
632         elem.isDominator = true;
633         dom = dom.getImmediateDominator();
634       }
635       path.add(elem);
636     }
637     Collections.reverse(path);
638     return path;
639   }
640 
641   /**
642    * Returns the next instance to GC root from this object and a string
643    * description of which field of that object refers to the given instance.
644    * Returns null if the given instance has no next instance to the gc root.
645    */
getNextPathElementToGcRoot(AhatInstance inst)646   private static PathElement getNextPathElementToGcRoot(AhatInstance inst) {
647     if (inst.isRoot()) {
648       return null;
649     }
650     return new PathElement(inst.mNextInstanceToGcRoot, inst.mNextInstanceToGcRootField);
651   }
652 
653   /**
654    * Returns a human-readable identifier for this object.
655    * For class objects, the string is the class name.
656    * For class instances, the string is the class name followed by '@' and the
657    * hex id of the instance.
658    * For array instances, the string is the array type followed by the size in
659    * square brackets, followed by '@' and the hex id of the instance.
660    *
661    * @return human-readable identifier for this object
662    */
toString()663   @Override public abstract String toString();
664 
665   /**
666    * Read the byte[] value from an hprof Instance.
667    * Returns null if the instance is not a byte array.
668    */
asByteArray()669   byte[] asByteArray() {
670     return null;
671   }
672 
setBaseline(AhatInstance baseline)673   void setBaseline(AhatInstance baseline) {
674     mBaseline = baseline;
675   }
676 
getBaseline()677   @Override public AhatInstance getBaseline() {
678     return mBaseline;
679   }
680 
isPlaceHolder()681   @Override public boolean isPlaceHolder() {
682     return false;
683   }
684 
685   /**
686    * Returns a new place holder instance corresponding to this instance.
687    */
newPlaceHolderInstance()688   AhatInstance newPlaceHolderInstance() {
689     return new AhatPlaceHolderInstance(this);
690   }
691 
setTemporaryUserData(Object state)692   void setTemporaryUserData(Object state) {
693     mTemporaryUserData = state;
694   }
695 
getTemporaryUserData()696   Object getTemporaryUserData() {
697     return mTemporaryUserData;
698   }
699 
700   /**
701    * Determine the reachability of the all instances reachable from the given
702    * root instance. Initializes the following fields:
703    *   mReachability
704    *   mNextInstanceToGcRoot
705    *   mNextInstanceToGcRootField
706    *   mReverseReferences
707    *
708    * @param progress used to track progress of the traversal.
709    * @param numInsts upper bound on the total number of instances reachable
710    *                 from the root, solely used for the purposes of tracking
711    *                 progress.
712    */
computeReachability(SuperRoot root, Progress progress, long numInsts)713   static void computeReachability(SuperRoot root, Progress progress, long numInsts) {
714     // Start by doing a breadth first search through strong references.
715     // Then continue the breadth first through each weaker kind of reference.
716     progress.start("Computing reachability", numInsts);
717     EnumMap<Reachability, Queue<Reference>> queues = new EnumMap<>(Reachability.class);
718     for (Reachability reachability : Reachability.values()) {
719       queues.put(reachability, new ArrayDeque<Reference>());
720     }
721 
722     for (Reference ref : root.getReferences()) {
723       queues.get(Reachability.STRONG).add(ref);
724     }
725 
726     for (Reachability reachability : Reachability.values()) {
727       Queue<Reference> queue = queues.get(reachability);
728       while (!queue.isEmpty()) {
729         Reference ref = queue.poll();
730         if (ref.ref.mReachability == Reachability.UNREACHABLE) {
731           // This is the first time we have seen ref.ref.
732           progress.advance();
733           ref.ref.mReachability = reachability;
734           ref.ref.mNextInstanceToGcRoot = ref.src;
735           ref.ref.mNextInstanceToGcRootField = ref.field;
736           ref.ref.mReverseReferences = new ArrayList<AhatInstance>();
737 
738           for (Reference childRef : ref.ref.getReferences()) {
739             if (childRef.reachability.notWeakerThan(reachability)) {
740               queue.add(childRef);
741             } else {
742               queues.get(childRef.reachability).add(childRef);
743             }
744           }
745         }
746 
747         // Note: We specifically exclude 'root' from the reverse references
748         // because it is a fake SuperRoot instance not present in the original
749         // heap dump.
750         if (ref.src != root) {
751           ref.ref.mReverseReferences.add(ref.src);
752         }
753       }
754     }
755     progress.done();
756   }
757 
758   /**
759    * Recursively compute the retained size of the given instance and all
760    * other instances it dominates.
761    */
computeRetainedSize(AhatInstance inst, int numHeaps)762   static void computeRetainedSize(AhatInstance inst, int numHeaps) {
763     // Note: We can't use a recursive implementation because it can lead to
764     // stack overflow. Use an iterative implementation instead.
765     //
766     // Objects not yet processed will have mRetainedSizes set to null.
767     // Once prepared, an object will have mRetaiedSizes set to an array of 0
768     // sizes.
769     Deque<AhatInstance> deque = new ArrayDeque<AhatInstance>();
770     deque.push(inst);
771 
772     while (!deque.isEmpty()) {
773       inst = deque.pop();
774       if (inst.mRetainedSizes == null) {
775         inst.mRetainedSizes = new Size[numHeaps];
776         for (int i = 0; i < numHeaps; i++) {
777           inst.mRetainedSizes[i] = Size.ZERO;
778         }
779         if (!(inst instanceof SuperRoot)) {
780           inst.mRetainedSizes[inst.mHeap.getIndex()] =
781             inst.mRetainedSizes[inst.mHeap.getIndex()].plus(inst.getSize());
782         }
783         deque.push(inst);
784         for (AhatInstance dominated : inst.mDominated) {
785           deque.push(dominated);
786         }
787       } else {
788         for (AhatInstance dominated : inst.mDominated) {
789           for (int i = 0; i < numHeaps; i++) {
790             inst.mRetainedSizes[i] = inst.mRetainedSizes[i].plus(dominated.mRetainedSizes[i]);
791           }
792         }
793       }
794     }
795   }
796 
getReferencesForDominators(Reachability retained)797   Iterable<AhatInstance> getReferencesForDominators(Reachability retained) {
798     return new DominatorReferenceIterator(retained, getReferences());
799   }
800 
setDominator(AhatInstance dominator)801   void setDominator(AhatInstance dominator) {
802     mImmediateDominator = dominator;
803     mImmediateDominator.mDominated.add(this);
804   }
805 }
806