| /* |
| * 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.tools.perflib.heap.ClassObj; |
| import com.android.tools.perflib.heap.Instance; |
| import com.android.tools.perflib.heap.RootObj; |
| import java.awt.image.BufferedImage; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| |
| public abstract class AhatInstance implements Diffable<AhatInstance> { |
| private long mId; |
| private long mSize; |
| private long mTotalRetainedSize; |
| private long mRetainedSizes[]; // Retained size indexed by heap index |
| private boolean mIsReachable; |
| private AhatHeap mHeap; |
| private AhatInstance mImmediateDominator; |
| private AhatInstance mNextInstanceToGcRoot; |
| private String mNextInstanceToGcRootField = "???"; |
| private AhatClassObj mClassObj; |
| private AhatInstance[] mHardReverseReferences; |
| private AhatInstance[] mSoftReverseReferences; |
| private Site mSite; |
| |
| // If this instance is a root, mRootTypes contains a set of the root types. |
| // If this instance is not a root, mRootTypes is null. |
| private List<String> mRootTypes; |
| |
| // List of instances this instance immediately dominates. |
| private List<AhatInstance> mDominated = new ArrayList<AhatInstance>(); |
| |
| private AhatInstance mBaseline; |
| |
| public AhatInstance(long id) { |
| mId = id; |
| mBaseline = this; |
| } |
| |
| /** |
| * Initializes this AhatInstance based on the given perflib instance. |
| * The AhatSnapshot should be used to look up AhatInstances and AhatHeaps. |
| * There is no guarantee that the AhatInstances returned by |
| * snapshot.findInstance have been initialized yet. |
| */ |
| void initialize(AhatSnapshot snapshot, Instance inst) { |
| mId = inst.getId(); |
| mSize = inst.getSize(); |
| mTotalRetainedSize = inst.getTotalRetainedSize(); |
| mIsReachable = inst.isReachable(); |
| |
| List<AhatHeap> heaps = snapshot.getHeaps(); |
| mRetainedSizes = new long[heaps.size()]; |
| for (AhatHeap heap : heaps) { |
| mRetainedSizes[heap.getIndex()] = inst.getRetainedSize(heap.getIndex()); |
| } |
| |
| mHeap = snapshot.getHeap(inst.getHeap().getName()); |
| |
| Instance dom = inst.getImmediateDominator(); |
| if (dom == null || dom instanceof RootObj) { |
| mImmediateDominator = null; |
| } else { |
| mImmediateDominator = snapshot.findInstance(dom.getId()); |
| mImmediateDominator.mDominated.add(this); |
| } |
| |
| ClassObj clsObj = inst.getClassObj(); |
| if (clsObj != null) { |
| mClassObj = snapshot.findClassObj(clsObj.getId()); |
| } |
| |
| // A couple notes about reverse references: |
| // * perflib sometimes returns unreachable reverse references. If |
| // snapshot.findInstance returns null, it means the reverse reference is |
| // not reachable, so we filter it out. |
| // * We store the references as AhatInstance[] instead of |
| // ArrayList<AhatInstance> because it saves a lot of space and helps |
| // with performance when there are a lot of AhatInstances. |
| ArrayList<AhatInstance> ahatRefs = new ArrayList<AhatInstance>(); |
| ahatRefs = new ArrayList<AhatInstance>(); |
| for (Instance ref : inst.getHardReverseReferences()) { |
| AhatInstance ahat = snapshot.findInstance(ref.getId()); |
| if (ahat != null) { |
| ahatRefs.add(ahat); |
| } |
| } |
| mHardReverseReferences = new AhatInstance[ahatRefs.size()]; |
| ahatRefs.toArray(mHardReverseReferences); |
| |
| List<Instance> refs = inst.getSoftReverseReferences(); |
| ahatRefs.clear(); |
| if (refs != null) { |
| for (Instance ref : refs) { |
| AhatInstance ahat = snapshot.findInstance(ref.getId()); |
| if (ahat != null) { |
| ahatRefs.add(ahat); |
| } |
| } |
| } |
| mSoftReverseReferences = new AhatInstance[ahatRefs.size()]; |
| ahatRefs.toArray(mSoftReverseReferences); |
| } |
| |
| /** |
| * Returns a unique identifier for the instance. |
| */ |
| public long getId() { |
| return mId; |
| } |
| |
| /** |
| * Returns the shallow number of bytes this object takes up. |
| */ |
| public long getSize() { |
| return mSize; |
| } |
| |
| /** |
| * Returns the number of bytes belonging to the given heap that this instance |
| * retains. |
| */ |
| public long getRetainedSize(AhatHeap heap) { |
| int index = heap.getIndex(); |
| return 0 <= index && index < mRetainedSizes.length ? mRetainedSizes[heap.getIndex()] : 0; |
| } |
| |
| /** |
| * Returns the total number of bytes this instance retains. |
| */ |
| public long getTotalRetainedSize() { |
| return mTotalRetainedSize; |
| } |
| |
| /** |
| * Returns whether this object is strongly-reachable. |
| */ |
| public boolean isReachable() { |
| return mIsReachable; |
| } |
| |
| /** |
| * Returns the heap that this instance is allocated on. |
| */ |
| public AhatHeap getHeap() { |
| return mHeap; |
| } |
| |
| /** |
| * Returns true if this instance is marked as a root instance. |
| */ |
| public boolean isRoot() { |
| return mRootTypes != null; |
| } |
| |
| /** |
| * Marks this instance as being a root of the given type. |
| */ |
| void addRootType(String type) { |
| if (mRootTypes == null) { |
| mRootTypes = new ArrayList<String>(); |
| mRootTypes.add(type); |
| } else if (!mRootTypes.contains(type)) { |
| mRootTypes.add(type); |
| } |
| } |
| |
| /** |
| * Returns a list of string descriptions of the root types of this object. |
| * Returns null if this object is not a root. |
| */ |
| public Collection<String> getRootTypes() { |
| return mRootTypes; |
| } |
| |
| /** |
| * Returns the immediate dominator of this instance. |
| * Returns null if this is a root instance. |
| */ |
| public AhatInstance getImmediateDominator() { |
| return mImmediateDominator; |
| } |
| |
| /** |
| * Returns a list of those objects immediately dominated by the given |
| * instance. |
| */ |
| public List<AhatInstance> getDominated() { |
| return mDominated; |
| } |
| |
| /** |
| * Returns the site where this instance was allocated. |
| */ |
| public Site getSite() { |
| return mSite; |
| } |
| |
| /** |
| * Sets the allocation site of this instance. |
| */ |
| void setSite(Site site) { |
| mSite = site; |
| } |
| |
| /** |
| * Returns true if the given 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. |
| */ |
| public AhatClassObj asClassObj() { |
| // Overridden by AhatClassObj. |
| return null; |
| } |
| |
| /** |
| * Returns the class object instance for the class of this object. |
| */ |
| public AhatClassObj getClassObj() { |
| return mClassObj; |
| } |
| |
| /** |
| * Returns the name of the class this object belongs to. |
| */ |
| public String getClassName() { |
| AhatClassObj classObj = getClassObj(); |
| return classObj == null ? "???" : classObj.getName(); |
| } |
| |
| /** |
| * Returns 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. |
| */ |
| public AhatArrayInstance asArrayInstance() { |
| // Overridden by AhatArrayInstance. |
| return null; |
| } |
| |
| /** |
| * Returns true if the given 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. |
| */ |
| public AhatClassInstance asClassInstance() { |
| return null; |
| } |
| |
| /** |
| * Return the referent associated with this instance. |
| * This is relevent for instances of java.lang.ref.Reference. |
| * Returns null if the instance has no referent associated with it. |
| */ |
| public AhatInstance getReferent() { |
| // Overridden by AhatClassInstance. |
| return null; |
| } |
| |
| /** |
| * Returns a list of objects with hard references to this object. |
| */ |
| public List<AhatInstance> getHardReverseReferences() { |
| return Arrays.asList(mHardReverseReferences); |
| } |
| |
| /** |
| * Returns a list of objects with soft references to this object. |
| */ |
| public List<AhatInstance> getSoftReverseReferences() { |
| return Arrays.asList(mSoftReverseReferences); |
| } |
| |
| /** |
| * Returns the value of a field of an instance. |
| * Returns null if the field value is null, the field couldn't be read, or |
| * there are multiple fields with the same name. |
| */ |
| 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, or if the field couldn't be read. |
| */ |
| public AhatInstance getRefField(String fieldName) { |
| // Overridden by AhatClassInstance. |
| return null; |
| } |
| |
| /** |
| * Assuming inst represents a DexCache object, return the dex location for |
| * that dex cache. Returns null if the given instance doesn't represent a |
| * DexCache object or the location could not be found. |
| * If maxChars is non-negative, the returned location is truncated to |
| * maxChars in length. |
| */ |
| public String getDexCacheLocation(int maxChars) { |
| return null; |
| } |
| |
| /** |
| * Return the bitmap instance associated with this object, or null if there |
| * is none. This works for android.graphics.Bitmap instances and their |
| * underlying Byte[] instances. |
| */ |
| public AhatInstance getAssociatedBitmapInstance() { |
| return null; |
| } |
| |
| /** |
| * Read the string value from this instance. |
| * Returns null if this object can't be interpreted as a string. |
| * The returned string is truncated to maxChars characters. |
| * If maxChars is negative, the returned string is not truncated. |
| */ |
| 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; |
| } |
| |
| /** |
| * Reads the string value from an hprof Instance. |
| * Returns null if the object can't be interpreted as a string. |
| */ |
| public String asString() { |
| return asString(-1); |
| } |
| |
| /** |
| * Return the bitmap associated with the given instance, if any. |
| * This is relevant for instances of android.graphics.Bitmap and byte[]. |
| * Returns null if there is no bitmap associated with the given instance. |
| */ |
| public BufferedImage asBitmap() { |
| return null; |
| } |
| |
| /** |
| * Returns a sample path from a GC root to this instance. |
| * This instance is included as the last element of the path with an empty |
| * field description. |
| */ |
| public List<PathElement> getPathFromGcRoot() { |
| 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) { |
| AhatInstance parent = inst.mNextInstanceToGcRoot; |
| if (parent == null) { |
| return null; |
| } |
| return new PathElement(inst.mNextInstanceToGcRoot, inst.mNextInstanceToGcRootField); |
| } |
| |
| void setNextInstanceToGcRoot(AhatInstance inst, String field) { |
| mNextInstanceToGcRoot = inst; |
| mNextInstanceToGcRootField = field; |
| } |
| |
| /** 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. |
| */ |
| @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; |
| } |
| |
| public 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); |
| } |
| } |