/* * 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); } }