Distinguish between soft/weak/phantom/etc references in ahat.
Instead of grouping all the different kinds of references into a single
category of "weak".
Bug: 79131879
Test: m ahat-test
Change-Id: I800f70b7f10386510e20d9d7b8a76069ddd38982
diff --git a/tools/ahat/etc/ahat_api.txt b/tools/ahat/etc/ahat_api.txt
index 6fc62e7..95da10f 100644
--- a/tools/ahat/etc/ahat_api.txt
+++ b/tools/ahat/etc/ahat_api.txt
@@ -65,19 +65,21 @@
method public java.util.List<com.android.ahat.heapdump.AhatInstance> getDominated();
method public java.lang.Object getDominatorsComputationState();
method public com.android.ahat.heapdump.Value getField(java.lang.String);
- method public java.util.List<com.android.ahat.heapdump.AhatInstance> getHardReverseReferences();
+ method public deprecated java.util.List<com.android.ahat.heapdump.AhatInstance> getHardReverseReferences();
method public com.android.ahat.heapdump.AhatHeap getHeap();
method public long getId();
method public com.android.ahat.heapdump.AhatInstance getImmediateDominator();
method public java.util.List<com.android.ahat.heapdump.PathElement> getPathFromGcRoot();
+ method public com.android.ahat.heapdump.Reachability getReachability();
method public com.android.ahat.heapdump.AhatInstance getRefField(java.lang.String);
method public java.lang.Iterable<? extends com.android.ahat.dominators.DominatorsComputation.Node> getReferencesForDominators();
method public com.android.ahat.heapdump.AhatInstance getReferent();
method public com.android.ahat.heapdump.Size getRetainedSize(com.android.ahat.heapdump.AhatHeap);
+ method public java.util.List<com.android.ahat.heapdump.AhatInstance> getReverseReferences();
method public java.util.Collection<com.android.ahat.heapdump.RootType> getRootTypes();
method public com.android.ahat.heapdump.Site getSite();
method public com.android.ahat.heapdump.Size getSize();
- method public java.util.List<com.android.ahat.heapdump.AhatInstance> getSoftReverseReferences();
+ method public deprecated java.util.List<com.android.ahat.heapdump.AhatInstance> getSoftReverseReferences();
method public com.android.ahat.heapdump.Size getTotalRetainedSize();
method public boolean isArrayInstance();
method public boolean isClassInstance();
@@ -86,7 +88,7 @@
method public boolean isRoot();
method public boolean isStronglyReachable();
method public boolean isUnreachable();
- method public boolean isWeaklyReachable();
+ method public deprecated boolean isWeaklyReachable();
method public void setDominator(com.android.ahat.dominators.DominatorsComputation.Node);
method public void setDominatorsComputationState(java.lang.Object);
method public abstract java.lang.String toString();
@@ -173,6 +175,17 @@
field public boolean isDominator;
}
+ public final class Reachability extends java.lang.Enum {
+ method public static com.android.ahat.heapdump.Reachability valueOf(java.lang.String);
+ method public static final com.android.ahat.heapdump.Reachability[] values();
+ enum_constant public static final com.android.ahat.heapdump.Reachability FINALIZER;
+ enum_constant public static final com.android.ahat.heapdump.Reachability PHANTOM;
+ enum_constant public static final com.android.ahat.heapdump.Reachability SOFT;
+ enum_constant public static final com.android.ahat.heapdump.Reachability STRONG;
+ enum_constant public static final com.android.ahat.heapdump.Reachability UNREACHABLE;
+ enum_constant public static final com.android.ahat.heapdump.Reachability WEAK;
+ }
+
public final class RootType extends java.lang.Enum {
method public static com.android.ahat.heapdump.RootType valueOf(java.lang.String);
method public static final com.android.ahat.heapdump.RootType[] values();
diff --git a/tools/ahat/src/main/com/android/ahat/ObjectHandler.java b/tools/ahat/src/main/com/android/ahat/ObjectHandler.java
index bfd5d5c..c099da8 100644
--- a/tools/ahat/src/main/com/android/ahat/ObjectHandler.java
+++ b/tools/ahat/src/main/com/android/ahat/ObjectHandler.java
@@ -44,8 +44,7 @@
private static final String DOMINATED_OBJECTS_ID = "dominated";
private static final String INSTANCE_FIELDS_ID = "ifields";
private static final String STATIC_FIELDS_ID = "sfields";
- private static final String HARD_REFS_ID = "refs";
- private static final String SOFT_REFS_ID = "srefs";
+ private static final String REFS_ID = "refs";
private AhatSnapshot mSnapshot;
@@ -223,24 +222,12 @@
private static void printReferences(Doc doc, Query query, AhatInstance inst) {
doc.section("Objects with References to this Object");
- if (inst.getHardReverseReferences().isEmpty()) {
+ if (inst.getReverseReferences().isEmpty()) {
doc.println(DocString.text("(none)"));
} else {
doc.table(new Column("Object"));
- List<AhatInstance> references = inst.getHardReverseReferences();
- SubsetSelector<AhatInstance> selector = new SubsetSelector(query, HARD_REFS_ID, references);
- for (AhatInstance ref : selector.selected()) {
- doc.row(Summarizer.summarize(ref));
- }
- doc.end();
- selector.render(doc);
- }
-
- if (!inst.getSoftReverseReferences().isEmpty()) {
- doc.section("Objects with Soft References to this Object");
- doc.table(new Column("Object"));
- List<AhatInstance> references = inst.getSoftReverseReferences();
- SubsetSelector<AhatInstance> selector = new SubsetSelector(query, SOFT_REFS_ID, references);
+ List<AhatInstance> references = inst.getReverseReferences();
+ SubsetSelector<AhatInstance> selector = new SubsetSelector(query, REFS_ID, references);
for (AhatInstance ref : selector.selected()) {
doc.row(Summarizer.summarize(ref));
}
diff --git a/tools/ahat/src/main/com/android/ahat/Summarizer.java b/tools/ahat/src/main/com/android/ahat/Summarizer.java
index ae0776a..6fc399d 100644
--- a/tools/ahat/src/main/com/android/ahat/Summarizer.java
+++ b/tools/ahat/src/main/com/android/ahat/Summarizer.java
@@ -17,6 +17,7 @@
package com.android.ahat;
import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.Reachability;
import com.android.ahat.heapdump.Site;
import com.android.ahat.heapdump.Value;
import java.net.URI;
@@ -50,11 +51,10 @@
formatted.append(DocString.removed("del "));
}
- // Annotate unreachable objects as such.
- if (inst.isWeaklyReachable()) {
- formatted.append("weak ");
- } else if (inst.isUnreachable()) {
- formatted.append("unreachable ");
+ // Annotate non-strongly reachable objects as such.
+ Reachability reachability = inst.getReachability();
+ if (reachability != Reachability.STRONG) {
+ formatted.append(reachability.toString() + " ");
}
// Annotate roots as roots.
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatArrayInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatArrayInstance.java
index 9c80802..cf6cf93 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatArrayInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatArrayInstance.java
@@ -240,7 +240,10 @@
if (value != null) {
assert value.isAhatInstance();
String field = "[" + Integer.toString(index) + "]";
- return new Reference(AhatArrayInstance.this, field, value.asAhatInstance(), true);
+ return new Reference(AhatArrayInstance.this,
+ field,
+ value.asAhatInstance(),
+ Reachability.STRONG);
}
return null;
}
@@ -324,7 +327,7 @@
@Override public AhatInstance getAssociatedBitmapInstance() {
if (mByteArray != null) {
- List<AhatInstance> refs = getHardReverseReferences();
+ List<AhatInstance> refs = getReverseReferences();
if (refs.size() == 1) {
AhatInstance ref = refs.get(0);
return ref.getAssociatedBitmapInstance();
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
index c82ef20..f377ae3 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
@@ -104,10 +104,7 @@
@Override
Iterable<Reference> getReferences() {
- if (isInstanceOfClass("java.lang.ref.Reference")) {
- return new WeakReferentReferenceIterator();
- }
- return new StrongReferenceIterator();
+ return new ReferenceIterator();
}
/**
@@ -352,59 +349,48 @@
}
/**
- * A Reference iterator that iterates over the fields of this instance
- * assuming all field references are strong references.
+ * Returns the reachability type associated with this instance.
+ * For example, returns Reachability.WEAK for an instance of
+ * java.lang.ref.WeakReference.
*/
- private class StrongReferenceIterator implements Iterable<Reference>,
- Iterator<Reference> {
- private Iterator<FieldValue> mIter = getInstanceFields().iterator();
- private Reference mNext = null;
-
- @Override
- public boolean hasNext() {
- while (mNext == null && mIter.hasNext()) {
- FieldValue field = mIter.next();
- if (field.value != null && field.value.isAhatInstance()) {
- AhatInstance ref = field.value.asAhatInstance();
- mNext = new Reference(AhatClassInstance.this, "." + field.name, ref, true);
- }
+ private Reachability getJavaLangRefType() {
+ AhatClassObj cls = getClassObj();
+ while (cls != null) {
+ switch (cls.getName()) {
+ case "java.lang.ref.PhantomReference": return Reachability.PHANTOM;
+ case "java.lang.ref.WeakReference": return Reachability.WEAK;
+ case "java.lang.ref.FinalizerReference": return Reachability.FINALIZER;
+ case "java.lang.ref.SoftReference": return Reachability.SOFT;
}
- return mNext != null;
+ cls = cls.getSuperClassObj();
}
-
- @Override
- public Reference next() {
- if (!hasNext()) {
- throw new NoSuchElementException();
- }
- Reference next = mNext;
- mNext = null;
- return next;
- }
-
- @Override
- public Iterator<Reference> iterator() {
- return this;
- }
+ return Reachability.STRONG;
}
/**
- * A Reference iterator that iterates over the fields of a subclass of
- * java.lang.ref.Reference, where the 'referent' field is considered weak.
+ * A Reference iterator that iterates over the fields of this instance.
*/
- private class WeakReferentReferenceIterator implements Iterable<Reference>,
- Iterator<Reference> {
- private Iterator<FieldValue> mIter = getInstanceFields().iterator();
+ private class ReferenceIterator implements Iterable<Reference>,
+ Iterator<Reference> {
+ private final Iterator<FieldValue> mIter = getInstanceFields().iterator();
private Reference mNext = null;
+ // If we are iterating over a subclass of java.lang.ref.Reference, the
+ // 'referent' field doesn't have strong reachability. mJavaLangRefType
+ // describes what type of java.lang.ref.Reference subinstance this is.
+ private final Reachability mJavaLangRefType = getJavaLangRefType();
+
@Override
public boolean hasNext() {
while (mNext == null && mIter.hasNext()) {
FieldValue field = mIter.next();
if (field.value != null && field.value.isAhatInstance()) {
- boolean strong = !field.name.equals("referent");
+ Reachability reachability = Reachability.STRONG;
+ if (mJavaLangRefType != Reachability.STRONG && "referent".equals(field.name)) {
+ reachability = mJavaLangRefType;
+ }
AhatInstance ref = field.value.asAhatInstance();
- mNext = new Reference(AhatClassInstance.this, "." + field.name, ref, strong);
+ mNext = new Reference(AhatClassInstance.this, "." + field.name, ref, reachability);
}
}
return mNext != null;
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassObj.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassObj.java
index 36ada28..765a411 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassObj.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassObj.java
@@ -131,7 +131,10 @@
FieldValue field = mStaticFieldValues[index];
Value value = field.value;
if (value != null && value.isAhatInstance()) {
- return new Reference(AhatClassObj.this, "." + field.name, value.asAhatInstance(), true);
+ return new Reference(AhatClassObj.this,
+ "." + field.name,
+ value.asAhatInstance(),
+ Reachability.STRONG);
}
return null;
}
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
index 95553a2..37ef6de 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
@@ -24,6 +24,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
+import java.util.EnumMap;
import java.util.List;
import java.util.Queue;
@@ -48,11 +49,11 @@
// Field initialized via addRegisterednativeSize.
private long mRegisteredNativeSize = 0;
- // Fields initialized in computeReverseReferences().
+ // Fields initialized in computeReachability().
+ private Reachability mReachability = Reachability.UNREACHABLE;
private AhatInstance mNextInstanceToGcRoot;
private String mNextInstanceToGcRootField;
- private ArrayList<AhatInstance> mHardReverseReferences;
- private ArrayList<AhatInstance> mSoftReverseReferences;
+ private ArrayList<AhatInstance> mReverseReferences;
// Fields initialized in DominatorsComputation.computeDominators().
// mDominated - the list of instances immediately dominated by this instance.
@@ -157,6 +158,15 @@
}
/**
+ * 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.
@@ -164,7 +174,7 @@
* @return true if the object is strongly reachable
*/
public boolean isStronglyReachable() {
- return mImmediateDominator != null;
+ return mReachability == Reachability.STRONG;
}
/**
@@ -178,10 +188,13 @@
* 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
*/
- public boolean isWeaklyReachable() {
- return !isStronglyReachable() && mNextInstanceToGcRoot != null;
+ @Deprecated public boolean isWeaklyReachable() {
+ return !isStronglyReachable() && !isUnreachable();
}
/**
@@ -193,7 +206,7 @@
* @return true if the object is completely unreachable
*/
public boolean isUnreachable() {
- return !isStronglyReachable() && !isWeaklyReachable();
+ return mReachability == Reachability.UNREACHABLE;
}
/**
@@ -215,7 +228,6 @@
* Returns true if this instance is a GC root.
*
* @return true if this instance is a GC root.
- * @see getRootTypes
*/
public boolean isRoot() {
return mRootTypes != 0;
@@ -374,28 +386,50 @@
}
/**
- * Returns a list of objects with (strong) references to this object.
+ * Returns a list of objects with any kind of reference to this object.
*
* @return the objects referencing this object
*/
- public List<AhatInstance> getHardReverseReferences() {
- if (mHardReverseReferences != null) {
- return mHardReverseReferences;
+ 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
*/
- public List<AhatInstance> getSoftReverseReferences() {
- if (mSoftReverseReferences != null) {
- return mSoftReverseReferences;
+ @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 Collections.emptyList();
+ return refs;
}
/**
@@ -598,82 +632,60 @@
}
/**
- * Initialize the reverse reference fields of this instance and all other
- * instances reachable from it. Initializes the following fields:
+ * Determine the reachability of the all instances reachable from the given
+ * root instance. Initializes the following fields:
+ * mReachability
* mNextInstanceToGcRoot
* mNextInstanceToGcRootField
- * mHardReverseReferences
- * mSoftReverseReferences
+ * 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 computeReverseReferences(SuperRoot root, Progress progress, long numInsts) {
+ static void computeReachability(SuperRoot root, Progress progress, long numInsts) {
// Start by doing a breadth first search through strong references.
- // Then continue the breadth first search through weak references.
- progress.start("Reversing references", numInsts);
- Queue<Reference> strong = new ArrayDeque<Reference>();
- Queue<Reference> weak = new ArrayDeque<Reference>();
+ // 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()) {
- strong.add(ref);
+ queues.get(Reachability.STRONG).add(ref);
}
- while (!strong.isEmpty()) {
- Reference ref = strong.poll();
- assert ref.strong;
+ 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>();
- if (ref.ref.mNextInstanceToGcRoot == null) {
- // This is the first time we have seen ref.ref.
- progress.advance();
- ref.ref.mNextInstanceToGcRoot = ref.src;
- ref.ref.mNextInstanceToGcRootField = ref.field;
- ref.ref.mHardReverseReferences = new ArrayList<AhatInstance>();
-
- for (Reference childRef : ref.ref.getReferences()) {
- if (childRef.strong) {
- strong.add(childRef);
- } else {
- weak.add(childRef);
+ for (Reference childRef : ref.ref.getReferences()) {
+ if (childRef.reachability.ordinal() <= reachability.ordinal()) {
+ 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.mHardReverseReferences.add(ref.src);
- }
- }
-
- while (!weak.isEmpty()) {
- Reference ref = weak.poll();
-
- if (ref.ref.mNextInstanceToGcRoot == null) {
- // This is the first time we have seen ref.ref.
- progress.advance();
- ref.ref.mNextInstanceToGcRoot = ref.src;
- ref.ref.mNextInstanceToGcRootField = ref.field;
- ref.ref.mHardReverseReferences = new ArrayList<AhatInstance>();
-
- for (Reference childRef : ref.ref.getReferences()) {
- weak.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);
}
}
-
- if (ref.strong) {
- ref.ref.mHardReverseReferences.add(ref.src);
- } else {
- if (ref.ref.mSoftReverseReferences == null) {
- ref.ref.mSoftReverseReferences = new ArrayList<AhatInstance>();
- }
- ref.ref.mSoftReverseReferences.add(ref.src);
- }
}
-
progress.done();
}
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java
index bc94047..d9c7a19 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java
@@ -55,7 +55,7 @@
}
}
- AhatInstance.computeReverseReferences(mSuperRoot, progress, mInstances.size());
+ AhatInstance.computeReachability(mSuperRoot, progress, mInstances.size());
DominatorsComputation.computeDominators(mSuperRoot, progress, mInstances.size());
AhatInstance.computeRetainedSize(mSuperRoot, mHeaps.size());
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/DominatorReferenceIterator.java b/tools/ahat/src/main/com/android/ahat/heapdump/DominatorReferenceIterator.java
index 0b99e49..8c8de23 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/DominatorReferenceIterator.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/DominatorReferenceIterator.java
@@ -37,7 +37,7 @@
public boolean hasNext() {
while (mNext == null && mIter.hasNext()) {
Reference ref = mIter.next();
- if (ref.strong) {
+ if (ref.reachability == Reachability.STRONG) {
mNext = ref.ref;
}
}
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/Reachability.java b/tools/ahat/src/main/com/android/ahat/heapdump/Reachability.java
new file mode 100644
index 0000000..8df6c8c
--- /dev/null
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/Reachability.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 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;
+
+/**
+ * Enum corresponding to the reachability of an instance.
+ * See {@link java.lang.ref} for a specification of the various kinds of
+ * reachibility. The enum constants are specified in decreasing order of
+ * strength.
+ */
+public enum Reachability {
+ /**
+ * The instance is strongly reachable.
+ */
+ STRONG("strong"),
+
+ /**
+ * The instance is softly reachable.
+ */
+ SOFT("soft"),
+
+ /**
+ * The instance is finalizer reachable, but is neither strongly nor softly
+ * reachable.
+ */
+ FINALIZER("finalizer"),
+
+ /**
+ * The instance is weakly reachable.
+ */
+ WEAK("weak"),
+
+ /**
+ * The instance is phantom reachable.
+ */
+ PHANTOM("phantom"),
+
+ /**
+ * The instance is unreachable.
+ */
+ UNREACHABLE("unreachable");
+
+ /**
+ * The name of the reachibility.
+ */
+ private final String name;
+
+ Reachability(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/Reference.java b/tools/ahat/src/main/com/android/ahat/heapdump/Reference.java
index f1340bd..2de76fd 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/Reference.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/Reference.java
@@ -20,19 +20,18 @@
* Reference represents a reference from 'src' to 'ref' through 'field'.
* Field is a string description for human consumption. This is typically
* either "." followed by the field name or an array subscript such as "[4]".
- * 'strong' is true if this is a strong reference, false if it is a
- * weak/soft/other reference.
+ * reachability describes whether the reference is strong/soft/weak/etc.
*/
class Reference {
public final AhatInstance src;
public final String field;
public final AhatInstance ref;
- public final boolean strong;
+ public final Reachability reachability;
- public Reference(AhatInstance src, String field, AhatInstance ref, boolean strong) {
+ public Reference(AhatInstance src, String field, AhatInstance ref, Reachability reachability) {
this.src = src;
this.field = field;
this.ref = ref;
- this.strong = strong;
+ this.reachability = reachability;
}
}
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/SuperRoot.java b/tools/ahat/src/main/com/android/ahat/heapdump/SuperRoot.java
index b01cfff..d06df90 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/SuperRoot.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/SuperRoot.java
@@ -54,7 +54,7 @@
@Override
public Reference get(int index) {
String field = ".roots[" + Integer.toString(index) + "]";
- return new Reference(SuperRoot.this, field, mRoots.get(index), true);
+ return new Reference(SuperRoot.this, field, mRoots.get(index), Reachability.STRONG);
}
};
}
diff --git a/tools/ahat/src/test-dump/DumpedStuff.java b/tools/ahat/src/test-dump/DumpedStuff.java
index 98ead07..804a3a3 100644
--- a/tools/ahat/src/test-dump/DumpedStuff.java
+++ b/tools/ahat/src/test-dump/DumpedStuff.java
@@ -136,6 +136,7 @@
public WeakReference aWeakReference = new WeakReference(anObject, referenceQueue);
public WeakReference aNullReferentReference = new WeakReference(null, referenceQueue);
public SoftReference aSoftReference = new SoftReference(new Object());
+ public Reference reachabilityReferenceChain;
public byte[] bigArray;
public ObjectTree[] gcPathArray = new ObjectTree[]{null, null,
new ObjectTree(
@@ -145,7 +146,7 @@
public Reference aLongStrongPathToSamplePathObject;
public WeakReference aShortWeakPathToSamplePathObject;
public WeakReference aWeakRefToGcRoot = new WeakReference(Main.class);
- public SoftReference aWeakChain = new SoftReference(new Reference(new Reference(new Object())));
+ public SoftReference aSoftChain = new SoftReference(new Reference(new Reference(new Object())));
public Object[] basicStringRef;
public AddedObject addedObject;
public UnchangedObject unchangedObject = new UnchangedObject();
@@ -157,4 +158,15 @@
public int[] modifiedArray;
public Object objectAllocatedAtKnownSite;
public Object objectAllocatedAtKnownSubSite;
+
+ // Allocate those objects that we need to not be GC'd before taking the heap
+ // dump.
+ public void shouldNotGc() {
+ reachabilityReferenceChain = new Reference(
+ new SoftReference(
+ new Reference(
+ new WeakReference(
+ new SoftReference(
+ new PhantomReference(new Object(), referenceQueue))))));
+ }
}
diff --git a/tools/ahat/src/test-dump/Main.java b/tools/ahat/src/test-dump/Main.java
index de36748..ca18fd8 100644
--- a/tools/ahat/src/test-dump/Main.java
+++ b/tools/ahat/src/test-dump/Main.java
@@ -49,6 +49,8 @@
stuff.basicStringRef = new Object[]{stuff.basicString};
}
+ stuff.shouldNotGc();
+
// Take a heap dump that will include that instance of DumpedStuff.
System.err.println("Dumping hprof data to " + file);
VMDebug.dumpHprofData(file);
diff --git a/tools/ahat/src/test/com/android/ahat/InstanceTest.java b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
index 8fbb884..9d33211 100644
--- a/tools/ahat/src/test/com/android/ahat/InstanceTest.java
+++ b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
@@ -21,6 +21,7 @@
import com.android.ahat.heapdump.AhatInstance;
import com.android.ahat.heapdump.AhatSnapshot;
import com.android.ahat.heapdump.PathElement;
+import com.android.ahat.heapdump.Reachability;
import com.android.ahat.heapdump.Size;
import com.android.ahat.heapdump.Value;
import java.io.IOException;
@@ -216,10 +217,31 @@
AhatInstance ref = dump.getDumpedAhatInstance("aSoftReference");
AhatInstance referent = ref.getReferent();
assertNotNull(referent);
+ assertEquals(Reachability.SOFT, referent.getReachability());
assertTrue(referent.isWeaklyReachable());
}
@Test
+ public void reachability() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance strong1 = dump.getDumpedAhatInstance("reachabilityReferenceChain");
+ AhatInstance soft1 = strong1.getField("referent").asAhatInstance();
+ AhatInstance strong2 = soft1.getField("referent").asAhatInstance();
+ AhatInstance weak1 = strong2.getField("referent").asAhatInstance();
+ AhatInstance soft2 = weak1.getField("referent").asAhatInstance();
+ AhatInstance phantom1 = soft2.getField("referent").asAhatInstance();
+ AhatInstance obj = phantom1.getField("referent").asAhatInstance();
+
+ assertEquals(Reachability.STRONG, strong1.getReachability());
+ assertEquals(Reachability.STRONG, soft1.getReachability());
+ assertEquals(Reachability.SOFT, strong2.getReachability());
+ assertEquals(Reachability.SOFT, weak1.getReachability());
+ assertEquals(Reachability.WEAK, soft2.getReachability());
+ assertEquals(Reachability.WEAK, phantom1.getReachability());
+ assertEquals(Reachability.PHANTOM, obj.getReachability());
+ }
+
+ @Test
public void gcRootPath() throws IOException {
TestDump dump = TestDump.getTestDump();
@@ -388,24 +410,31 @@
// We had a bug in the past where weak references to GC roots caused the
// roots to be incorrectly be considered weakly reachable.
+ assertEquals(Reachability.STRONG, root.getReachability());
assertTrue(root.isStronglyReachable());
assertFalse(root.isWeaklyReachable());
}
@Test
- public void weakReferenceChain() throws IOException {
+ public void softReferenceChain() throws IOException {
// If the only reference to a chain of strongly referenced objects is a
- // weak reference, then all of the objects should be considered weakly
+ // soft reference, then all of the objects should be considered softly
// reachable.
TestDump dump = TestDump.getTestDump();
- AhatInstance ref = dump.getDumpedAhatInstance("aWeakChain");
- AhatInstance weak1 = ref.getField("referent").asAhatInstance();
- AhatInstance weak2 = weak1.getField("referent").asAhatInstance();
- AhatInstance weak3 = weak2.getField("referent").asAhatInstance();
+ AhatInstance ref = dump.getDumpedAhatInstance("aSoftChain");
+ AhatInstance soft1 = ref.getField("referent").asAhatInstance();
+ AhatInstance soft2 = soft1.getField("referent").asAhatInstance();
+ AhatInstance soft3 = soft2.getField("referent").asAhatInstance();
assertTrue(ref.isStronglyReachable());
- assertTrue(weak1.isWeaklyReachable());
- assertTrue(weak2.isWeaklyReachable());
- assertTrue(weak3.isWeaklyReachable());
+ assertEquals(Reachability.SOFT, soft1.getReachability());
+ assertEquals(Reachability.SOFT, soft2.getReachability());
+ assertEquals(Reachability.SOFT, soft3.getReachability());
+
+ // Test the deprecated isWeaklyReachable API, which interprets weak as any
+ // kind of phantom/finalizer/weak/soft reference.
+ assertTrue(soft1.isWeaklyReachable());
+ assertTrue(soft2.isWeaklyReachable());
+ assertTrue(soft3.isWeaklyReachable());
}
@Test
@@ -414,6 +443,8 @@
AhatInstance obj = dump.getDumpedAhatInstance("anObject");
AhatInstance ref = dump.getDumpedAhatInstance("aReference");
AhatInstance weak = dump.getDumpedAhatInstance("aWeakReference");
+ assertTrue(obj.getReverseReferences().contains(ref));
+ assertTrue(obj.getReverseReferences().contains(weak));
assertTrue(obj.getHardReverseReferences().contains(ref));
assertFalse(obj.getHardReverseReferences().contains(weak));
assertFalse(obj.getSoftReverseReferences().contains(ref));