| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.ahat.heapdump; |
| |
| import com.android.ahat.progress.Progress; |
| import java.awt.image.BufferedImage; |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Deque; |
| import java.util.EnumMap; |
| import java.util.List; |
| import java.util.Queue; |
| |
| /** |
| * A Java instance from a parsed heap dump. It is the base class used for all |
| * kinds of Java instances, including normal Java objects, class objects, and |
| * arrays. |
| */ |
| public abstract class AhatInstance implements Diffable<AhatInstance> { |
| // The id of this instance from the heap dump. |
| private final long mId; |
| |
| // Fields initialized in initialize(). |
| private AhatHeap mHeap; |
| private AhatClassObj mClassObj; |
| private Site mSite; |
| |
| // Bit vector of the root types of this object. |
| private int mRootTypes; |
| |
| // Field initialized via addRegisterednativeSize. |
| private long mRegisteredNativeSize = 0; |
| |
| // Fields initialized in computeReachability(). |
| private Reachability mReachability = Reachability.UNREACHABLE; |
| private AhatInstance mNextInstanceToGcRoot; |
| private String mNextInstanceToGcRootField; |
| private ArrayList<AhatInstance> mReverseReferences; |
| |
| // Fields initialized in DominatorsComputation.computeDominators(). |
| // mDominated - the list of instances immediately dominated by this instance. |
| // mRetainedSizes - retained size indexed by heap index. |
| private AhatInstance mImmediateDominator; |
| private List<AhatInstance> mDominated = new ArrayList<AhatInstance>(); |
| private Size[] mRetainedSizes; |
| |
| // The baseline instance for purposes of diff. |
| private AhatInstance mBaseline; |
| |
| // temporary user data associated with this instance. This is used for a |
| // couple different purposes: |
| // 1. During parsing of instances, to store temporary field data. |
| // 2. During dominators computation, to store the dominators computation state. |
| private Object mTemporaryUserData; |
| |
| AhatInstance(long id) { |
| mId = id; |
| mBaseline = this; |
| } |
| |
| /** |
| * Initialize this AhatInstance based on the the given info. |
| */ |
| void initialize(AhatHeap heap, Site site, AhatClassObj classObj) { |
| mHeap = heap; |
| mSite = site; |
| mClassObj = classObj; |
| } |
| |
| /** |
| * Returns a unique identifier for this instance. |
| * |
| * @return id of the instance |
| */ |
| public long getId() { |
| return mId; |
| } |
| |
| /** |
| * Returns the number of bytes used for this object in the heap. |
| * The returned size is a shallow size for the object that does not include |
| * sizes of other objects dominated by this object. |
| * |
| * @return the shallow size of the object |
| */ |
| public Size getSize() { |
| return new Size(mClassObj.getInstanceSize() + getExtraJavaSize(), mRegisteredNativeSize); |
| } |
| |
| /** |
| * Returns the number of bytes taken up by this object on the Java heap |
| * beyond the standard instance size as recorded by the class of this |
| * instance. |
| * |
| * For example, class objects will have extra size for static fields and |
| * array objects will have extra size for the array elements. |
| */ |
| abstract long getExtraJavaSize(); |
| |
| /** |
| * Returns the number of bytes retained by this object in the given heap. |
| * The returned size includes the shallow size of this object and the size |
| * of all objects directly or indirectly retained by this object. Only those |
| * objects allocated on the given heap are included in the reported size. |
| * |
| * @param heap the heap to get the retained size for |
| * @return the retained size of the object |
| */ |
| public Size getRetainedSize(AhatHeap heap) { |
| int index = heap.getIndex(); |
| if (mRetainedSizes != null && 0 <= index && index < mRetainedSizes.length) { |
| return mRetainedSizes[heap.getIndex()]; |
| } |
| return Size.ZERO; |
| } |
| |
| /** |
| * Returns the total number of bytes retained by this object. The returned |
| * size includes the shallow size of this object and the size of all objects |
| * directly or indirectly retained by this object. |
| * |
| * @return the total retained size of the object |
| */ |
| public Size getTotalRetainedSize() { |
| Size size = Size.ZERO; |
| if (mRetainedSizes != null) { |
| for (int i = 0; i < mRetainedSizes.length; i++) { |
| size = size.plus(mRetainedSizes[i]); |
| } |
| } |
| return size; |
| } |
| |
| /** |
| * Increment the number of registered native bytes tied to this object. |
| */ |
| void addRegisteredNativeSize(long size) { |
| mRegisteredNativeSize += size; |
| } |
| |
| /** |
| * Returns the reachability of the instance. |
| * |
| * @return the reachability of the instance. |
| */ |
| public Reachability getReachability() { |
| return mReachability; |
| } |
| |
| /** |
| * Returns true if this object is strongly reachable. An object is strongly |
| * reachable if there exists a path of (strong) references from some root |
| * object to this object. |
| * |
| * @return true if the object is strongly reachable |
| */ |
| public boolean isStronglyReachable() { |
| return mReachability == Reachability.STRONG; |
| } |
| |
| /** |
| * Returns true if this object is reachable only through a |
| * soft/weak/phantom/finalizer reference. An object is weakly reachable if |
| * it is not strongly reachable but there still exists a path of references |
| * from some root object to this object. Because the object is not strongly |
| * reachable, any such path must contain a SoftReference, WeakReference, |
| * PhantomReference, or FinalizerReference somewhere along it. |
| * <p> |
| * Unlike a strongly reachable object, a weakly reachable object is allowed |
| * to be garbage collected. |
| * |
| * @deprecated Use {@link #getReachability()} instead, which can distinguish |
| * among soft, weak, phantom, and other kinds of references. |
| * |
| * @return true if the object is weakly reachable |
| */ |
| @Deprecated public boolean isWeaklyReachable() { |
| return !isStronglyReachable() && !isUnreachable(); |
| } |
| |
| /** |
| * Returns true if this object is completely unreachable. An object is |
| * completely unreachable if there is no path to the object from some root |
| * object, neither through strong nor soft/weak/phantom/finalizer |
| * references. |
| * |
| * @return true if the object is completely unreachable |
| */ |
| public boolean isUnreachable() { |
| return mReachability == Reachability.UNREACHABLE; |
| } |
| |
| /** |
| * Returns the heap that this instance is allocated on. |
| * |
| * @return heap the instance is allocated on |
| */ |
| public AhatHeap getHeap() { |
| return mHeap; |
| } |
| |
| /** |
| * Returns an iterator over the references this AhatInstance has to other |
| * AhatInstances. |
| */ |
| abstract Iterable<Reference> getReferences(); |
| |
| /** |
| * Returns true if this instance is a GC root. |
| * |
| * @return true if this instance is a GC root. |
| */ |
| public boolean isRoot() { |
| return mRootTypes != 0; |
| } |
| |
| /** |
| * Marks this instance as being a root of the given type. |
| */ |
| void addRootType(RootType type) { |
| mRootTypes |= type.mask; |
| } |
| |
| /** |
| * Returns a list of the root types of this object. |
| * Returns null if this object is not a root. |
| * |
| * @return list of the objects root types |
| */ |
| public Collection<RootType> getRootTypes() { |
| if (!isRoot()) { |
| return null; |
| } |
| |
| List<RootType> types = new ArrayList<RootType>(); |
| for (RootType type : RootType.values()) { |
| if ((mRootTypes & type.mask) != 0) { |
| types.add(type); |
| } |
| } |
| return types; |
| } |
| |
| /** |
| * Returns the immediate dominator of this instance. |
| * Returns null if this is a root instance. |
| * |
| * @return the immediate dominator of this instance |
| */ |
| public AhatInstance getImmediateDominator() { |
| if (mImmediateDominator instanceof SuperRoot) { |
| return null; |
| } |
| return mImmediateDominator; |
| } |
| |
| /** |
| * Returns a list of objects immediately dominated by this instance. |
| * |
| * @return list of immediately dominated objects |
| */ |
| public List<AhatInstance> getDominated() { |
| return mDominated; |
| } |
| |
| /** |
| * Returns the site where this instance was allocated. |
| * |
| * @return the object's allocation site |
| */ |
| public Site getSite() { |
| return mSite; |
| } |
| |
| /** |
| * Returns true if this instance is a class object |
| * |
| * @return true if this instance is a class object |
| */ |
| public boolean isClassObj() { |
| // Overridden by AhatClassObj. |
| return false; |
| } |
| |
| /** |
| * Returns this as an AhatClassObj if this is an AhatClassObj. |
| * Returns null if this is not an AhatClassObj. |
| * |
| * @return this instance as a class object |
| */ |
| public AhatClassObj asClassObj() { |
| // Overridden by AhatClassObj. |
| return null; |
| } |
| |
| /** |
| * Returns the class object for this instance. |
| * For example, if this object is an instance of java.lang.String, this |
| * method returns the AhatClassObj for java.lang.String. |
| * |
| * @return the instance's class object |
| */ |
| public AhatClassObj getClassObj() { |
| return mClassObj; |
| } |
| |
| /** |
| * Returns the name of the class this object belongs to. |
| * For example, if this object is an instance of java.lang.String, returns |
| * "java.lang.String". |
| * |
| * @return the name of this instance's class |
| */ |
| public String getClassName() { |
| AhatClassObj classObj = getClassObj(); |
| return classObj == null ? "???" : classObj.getName(); |
| } |
| |
| /** |
| * Returns true if this is an instance of a (subclass of a) class with the |
| * given name. |
| * |
| * @param className the name of the class to check for |
| * @return true if this is an instance of a (subclass of a) class with the |
| * given name |
| */ |
| public boolean isInstanceOfClass(String className) { |
| AhatClassObj cls = getClassObj(); |
| while (cls != null) { |
| if (className.equals(cls.getName())) { |
| return true; |
| } |
| cls = cls.getSuperClassObj(); |
| } |
| return false; |
| } |
| |
| /** |
| * Returns true if the given instance is an array instance. |
| * |
| * @return true if the given instance is an array instance |
| */ |
| public boolean isArrayInstance() { |
| // Overridden by AhatArrayInstance. |
| return false; |
| } |
| |
| /** |
| * Returns this as an AhatArrayInstance if this is an AhatArrayInstance. |
| * Returns null if this is not an AhatArrayInstance. |
| * |
| * @return this instance as an array instance |
| */ |
| public AhatArrayInstance asArrayInstance() { |
| // Overridden by AhatArrayInstance. |
| return null; |
| } |
| |
| /** |
| * Returns true if this instance is a class instance. |
| * |
| * @return true if this instance is a class instance |
| */ |
| public boolean isClassInstance() { |
| return false; |
| } |
| |
| /** |
| * Returns this as an AhatClassInstance if this is an AhatClassInstance. |
| * Returns null if this is not an AhatClassInstance. |
| * |
| * @return this instance as a class instance |
| */ |
| public AhatClassInstance asClassInstance() { |
| return null; |
| } |
| |
| /** |
| * Returns the <code>referent</code> associated with this instance. |
| * This is only relevant for instances of java.lang.ref.Reference or its |
| * subclasses. Returns null if the instance has no referent associated with |
| * it. |
| * |
| * @return the referent associated with this instance |
| */ |
| public AhatInstance getReferent() { |
| // Overridden by AhatClassInstance. |
| return null; |
| } |
| |
| /** |
| * Returns a list of objects with any kind of reference to this object. |
| * |
| * @return the objects referencing this object |
| */ |
| public List<AhatInstance> getReverseReferences() { |
| if (mReverseReferences != null) { |
| return mReverseReferences; |
| } |
| return Collections.emptyList(); |
| } |
| |
| /** |
| * Returns a list of objects with (strong) references to this object. |
| * |
| * @deprecated Use {@link #getReverseReferences()} instead. |
| * |
| * @return the objects referencing this object |
| */ |
| @Deprecated public List<AhatInstance> getHardReverseReferences() { |
| List<AhatInstance> refs = new ArrayList<AhatInstance>(); |
| for (AhatInstance ref : getReverseReferences()) { |
| if (ref.getReachability() == Reachability.STRONG && ref.getReferent() != this) { |
| refs.add(ref); |
| } |
| } |
| return refs; |
| } |
| |
| /** |
| * Returns a list of objects with soft/weak/phantom/finalizer references to |
| * this object. |
| * |
| * @deprecated Use {@link #getReverseReferences()} instead. |
| * |
| * @return the objects weakly referencing this object |
| */ |
| @Deprecated public List<AhatInstance> getSoftReverseReferences() { |
| List<AhatInstance> refs = new ArrayList<AhatInstance>(); |
| for (AhatInstance ref : getReverseReferences()) { |
| if (ref.getReachability() != Reachability.STRONG || ref.getReferent() == this) { |
| refs.add(ref); |
| } |
| } |
| return refs; |
| } |
| |
| /** |
| * Returns the value of a field of this instance. Returns null if the field |
| * value is null, the field couldn't be read, or there are multiple fields |
| * with the same name. |
| * |
| * @param fieldName the name of the field to get the value of |
| * @return the field value |
| */ |
| public Value getField(String fieldName) { |
| // Overridden by AhatClassInstance. |
| return null; |
| } |
| |
| /** |
| * Reads a reference field of this instance. Returns null if the field value |
| * is null, of primitive type, or if the field couldn't be read. There is no |
| * way using this method to distinguish between a reference field with value |
| * <code>null</code> and an invalid field. |
| * |
| * @param fieldName the name of the reference field to get the value of |
| * @return the reference field value |
| */ |
| public AhatInstance getRefField(String fieldName) { |
| // Overridden by AhatClassInstance. |
| return null; |
| } |
| |
| /** |
| * Returns the dex location associated with this object. Only applies to |
| * instances of dalvik.system.DexCache. If this is an instance of DexCache, |
| * returns the dex location for that dex cache. Otherwise returns null. |
| * If maxChars is non-negative, the returned location is truncated to |
| * maxChars in length. |
| * |
| * @param maxChars the maximum length of the returned string |
| * @return the dex location associated with this object |
| */ |
| public String getDexCacheLocation(int maxChars) { |
| return null; |
| } |
| |
| /** |
| * Returns the name of the Binder proxy interface associated with this object. |
| * Only applies to instances of android.os.BinderProxy. If this is an |
| * instance of BinderProxy, returns the fully qualified binder interface name, |
| * otherwise returns null. |
| * |
| * @return the name of the binder interface associated with this object |
| */ |
| public String getBinderProxyInterfaceName() { |
| return null; |
| } |
| |
| /** |
| * Returns the descriptor of the Binder token associated with this object. |
| * Only applies to instances of android.os.Binder. If this is an instance of |
| * android.os.Binder with a subclass of the name "descriptor$Stub", the |
| * object in question is a binder stub, and this function will return null. |
| * In that case, @see AhatInstance#getBinderStubInterfaceName |
| * |
| * @return the descriptor of this object, if it's a binder token |
| */ |
| public String getBinderTokenDescriptor() { |
| return null; |
| } |
| |
| /** |
| * Returns the name of the Binder stub interface associated with this object. |
| * Only applies to instances which are a subclass of android.os.Binder, |
| * and are an instance of class 'descriptor$Stub', where descriptor |
| * is the descriptor of the android.os.Binder object. |
| * |
| * @return the name of the binder interface associated with this object, |
| * or null if this is not a binder stub interface. |
| */ |
| public String getBinderStubInterfaceName() { |
| return null; |
| } |
| |
| /** |
| * Returns the android.graphics.Bitmap instance associated with this object. |
| * Instances of android.graphics.Bitmap return themselves. If this is a |
| * byte[] array containing pixel data for an instance of |
| * android.graphics.Bitmap, that instance of android.graphics.Bitmap is |
| * returned. Otherwise null is returned. |
| * |
| * @return the bitmap instance associated with this object |
| */ |
| public AhatInstance getAssociatedBitmapInstance() { |
| return null; |
| } |
| |
| /** |
| * Returns the class object that this object represents the overhead for. |
| * ART adds a fake byte[] $classOverhead static field to classes to show the |
| * overheads associated with the class. If this is one such byte[] instance, |
| * returns the class it is associated with. Otherwise null is returned. |
| * |
| * @return the class instance that this is the overhead for |
| */ |
| public AhatClassObj getAssociatedClassForOverhead() { |
| return null; |
| } |
| |
| /** |
| * Returns the (bounded-length) string associated with this instance. |
| * Applies to instances of java.lang.String, char[], and in some cases |
| * byte[]. Returns null if this object cannot be interpreted as a string. |
| * If maxChars is non-negative, the returned string is truncated to maxChars |
| * characters in length. |
| * |
| * @param maxChars the maximum length of the returned string |
| * @return the string associated with this instance |
| */ |
| public String asString(int maxChars) { |
| // By default instances can't be interpreted as a string. This method is |
| // overridden by AhatClassInstance and AhatArrayInstance for those cases |
| // when an instance can be interpreted as a string. |
| return null; |
| } |
| |
| /** |
| * Returns the string associated with this instance. Applies to instances of |
| * java.lang.String, char[], and in some cases byte[]. Returns null if this |
| * object cannot be interpreted as a string. |
| * |
| * @return the string associated with this instance |
| */ |
| public String asString() { |
| return asString(-1); |
| } |
| |
| /** |
| * Returns the bitmap pixel data associated with this instance. |
| * This is relevant for instances of android.graphics.Bitmap and byte[]. |
| * Returns null if there is no bitmap pixel data associated with the given |
| * instance. |
| * |
| * @return the bitmap pixel data associated with this image |
| */ |
| public BufferedImage asBitmap() { |
| return null; |
| } |
| |
| static class RegisteredNativeAllocation { |
| public AhatInstance referent; |
| public long size; |
| }; |
| |
| /** |
| * Return the registered native allocation that this instance represents, if |
| * any. This is relevant for instances of sun.misc.Cleaner. |
| */ |
| RegisteredNativeAllocation asRegisteredNativeAllocation() { |
| return null; |
| } |
| |
| /** |
| * Returns a sample path from a GC root to this instance. The first element |
| * of the returned path is a GC root object. This instance is included as |
| * the last element of the path with an empty field description. |
| * <p> |
| * If the instance is strongly reachable, a path of string references will |
| * be returned. If the instance is weakly reachable, the returned path will |
| * include a soft/weak/phantom/finalizer reference somewhere along it. |
| * Returns null if this instance is not reachable. |
| * |
| * @return sample path from a GC root to this instance |
| * @see PathElement |
| */ |
| public List<PathElement> getPathFromGcRoot() { |
| if (isUnreachable()) { |
| return null; |
| } |
| |
| List<PathElement> path = new ArrayList<PathElement>(); |
| |
| AhatInstance dom = this; |
| for (PathElement elem = new PathElement(this, ""); elem != null; |
| elem = getNextPathElementToGcRoot(elem.instance)) { |
| if (elem.instance.equals(dom)) { |
| elem.isDominator = true; |
| dom = dom.getImmediateDominator(); |
| } |
| path.add(elem); |
| } |
| Collections.reverse(path); |
| return path; |
| } |
| |
| /** |
| * Returns the next instance to GC root from this object and a string |
| * description of which field of that object refers to the given instance. |
| * Returns null if the given instance has no next instance to the gc root. |
| */ |
| private static PathElement getNextPathElementToGcRoot(AhatInstance inst) { |
| if (inst.isRoot()) { |
| return null; |
| } |
| return new PathElement(inst.mNextInstanceToGcRoot, inst.mNextInstanceToGcRootField); |
| } |
| |
| /** |
| * Returns a human-readable identifier for this object. |
| * For class objects, the string is the class name. |
| * For class instances, the string is the class name followed by '@' and the |
| * hex id of the instance. |
| * For array instances, the string is the array type followed by the size in |
| * square brackets, followed by '@' and the hex id of the instance. |
| * |
| * @return human-readable identifier for this object |
| */ |
| @Override public abstract String toString(); |
| |
| /** |
| * Read the byte[] value from an hprof Instance. |
| * Returns null if the instance is not a byte array. |
| */ |
| byte[] asByteArray() { |
| return null; |
| } |
| |
| void setBaseline(AhatInstance baseline) { |
| mBaseline = baseline; |
| } |
| |
| @Override public AhatInstance getBaseline() { |
| return mBaseline; |
| } |
| |
| @Override public boolean isPlaceHolder() { |
| return false; |
| } |
| |
| /** |
| * Returns a new place holder instance corresponding to this instance. |
| */ |
| AhatInstance newPlaceHolderInstance() { |
| return new AhatPlaceHolderInstance(this); |
| } |
| |
| void setTemporaryUserData(Object state) { |
| mTemporaryUserData = state; |
| } |
| |
| Object getTemporaryUserData() { |
| return mTemporaryUserData; |
| } |
| |
| /** |
| * Determine the reachability of the all instances reachable from the given |
| * root instance. Initializes the following fields: |
| * mReachability |
| * mNextInstanceToGcRoot |
| * mNextInstanceToGcRootField |
| * mReverseReferences |
| * |
| * @param progress used to track progress of the traversal. |
| * @param numInsts upper bound on the total number of instances reachable |
| * from the root, solely used for the purposes of tracking |
| * progress. |
| */ |
| static void computeReachability(SuperRoot root, Progress progress, long numInsts) { |
| // Start by doing a breadth first search through strong references. |
| // Then continue the breadth first through each weaker kind of reference. |
| progress.start("Computing reachability", numInsts); |
| EnumMap<Reachability, Queue<Reference>> queues = new EnumMap<>(Reachability.class); |
| for (Reachability reachability : Reachability.values()) { |
| queues.put(reachability, new ArrayDeque<Reference>()); |
| } |
| |
| for (Reference ref : root.getReferences()) { |
| queues.get(Reachability.STRONG).add(ref); |
| } |
| |
| for (Reachability reachability : Reachability.values()) { |
| Queue<Reference> queue = queues.get(reachability); |
| while (!queue.isEmpty()) { |
| Reference ref = queue.poll(); |
| if (ref.ref.mReachability == Reachability.UNREACHABLE) { |
| // This is the first time we have seen ref.ref. |
| progress.advance(); |
| ref.ref.mReachability = reachability; |
| ref.ref.mNextInstanceToGcRoot = ref.src; |
| ref.ref.mNextInstanceToGcRootField = ref.field; |
| ref.ref.mReverseReferences = new ArrayList<AhatInstance>(); |
| |
| for (Reference childRef : ref.ref.getReferences()) { |
| if (childRef.reachability.notWeakerThan(reachability)) { |
| queue.add(childRef); |
| } else { |
| queues.get(childRef.reachability).add(childRef); |
| } |
| } |
| } |
| |
| // Note: We specifically exclude 'root' from the reverse references |
| // because it is a fake SuperRoot instance not present in the original |
| // heap dump. |
| if (ref.src != root) { |
| ref.ref.mReverseReferences.add(ref.src); |
| } |
| } |
| } |
| progress.done(); |
| } |
| |
| /** |
| * Recursively compute the retained size of the given instance and all |
| * other instances it dominates. |
| */ |
| static void computeRetainedSize(AhatInstance inst, int numHeaps) { |
| // Note: We can't use a recursive implementation because it can lead to |
| // stack overflow. Use an iterative implementation instead. |
| // |
| // Objects not yet processed will have mRetainedSizes set to null. |
| // Once prepared, an object will have mRetaiedSizes set to an array of 0 |
| // sizes. |
| Deque<AhatInstance> deque = new ArrayDeque<AhatInstance>(); |
| deque.push(inst); |
| |
| while (!deque.isEmpty()) { |
| inst = deque.pop(); |
| if (inst.mRetainedSizes == null) { |
| inst.mRetainedSizes = new Size[numHeaps]; |
| for (int i = 0; i < numHeaps; i++) { |
| inst.mRetainedSizes[i] = Size.ZERO; |
| } |
| if (!(inst instanceof SuperRoot)) { |
| inst.mRetainedSizes[inst.mHeap.getIndex()] = |
| inst.mRetainedSizes[inst.mHeap.getIndex()].plus(inst.getSize()); |
| } |
| deque.push(inst); |
| for (AhatInstance dominated : inst.mDominated) { |
| deque.push(dominated); |
| } |
| } else { |
| for (AhatInstance dominated : inst.mDominated) { |
| for (int i = 0; i < numHeaps; i++) { |
| inst.mRetainedSizes[i] = inst.mRetainedSizes[i].plus(dominated.mRetainedSizes[i]); |
| } |
| } |
| } |
| } |
| } |
| |
| Iterable<AhatInstance> getReferencesForDominators(Reachability retained) { |
| return new DominatorReferenceIterator(retained, getReferences()); |
| } |
| |
| void setDominator(AhatInstance dominator) { |
| mImmediateDominator = dominator; |
| mImmediateDominator.mDominated.add(this); |
| } |
| } |