/* * 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 { // 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 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 mDominated = new ArrayList(); 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. *

* 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 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 getRootTypes() { if (!isRoot()) { return null; } List types = new ArrayList(); 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() { return mImmediateDominator; } /** * Returns a list of objects immediately dominated by this instance. * * @return list of immediately dominated objects */ public List 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 referent 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 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 getHardReverseReferences() { List refs = new ArrayList(); 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 getSoftReverseReferences() { List refs = new ArrayList(); 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 * null 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. *

* 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 getPathFromGcRoot() { if (isUnreachable()) { return null; } List path = new ArrayList(); 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> queues = new EnumMap<>(Reachability.class); for (Reachability reachability : Reachability.values()) { queues.put(reachability, new ArrayDeque()); } for (Reference ref : root.getReferences()) { queues.get(Reachability.STRONG).add(ref); } for (Reachability reachability : Reachability.values()) { Queue 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(); 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 deque = new ArrayDeque(); 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 getReferencesForDominators(Reachability retained) { return new DominatorReferenceIterator(retained, getReferences()); } void setDominator(AhatInstance dominator) { mImmediateDominator = dominator; mImmediateDominator.mDominated.add(this); } }