blob: e62fb4010cbd429ac49d703d4cedf70dfad7bd01 [file] [log] [blame]
/*
* 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);
}
}