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