summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Richard Uhler <ruhler@google.com> 2017-10-20 10:41:50 +0100
committer Richard Uhler <ruhler@google.com> 2017-10-26 16:18:45 +0100
commit1b79bbcdc1c9fee58cec0da6b19afd75d0bb47c9 (patch)
treeeece222b38346dd07a78c1783f92654140b91637
parent9041c606de11412a036bac2d2068d6f151a0fab7 (diff)
Fix bugs with strong versus weak reference paths.
This fixes a few issues with strong versus weak reference paths: * It was possible to have a path from gc root with a root in the middle of the path. * It was possible to have a path from gc root with a cycle, which would cause ahat to go into an infinite loop. * It was still possible to have a path from gc root through weak references when there existed other paths from gc root only through strong references. Bug: 64592321 Test: m ahat-test, with new tests added. Change-Id: I2d3cc8c5a398aad24d70c3a428bdc3a470ddb3aa
-rw-r--r--tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java73
-rw-r--r--tools/ahat/src/main/com/android/ahat/heapdump/SuperRoot.java2
-rw-r--r--tools/ahat/src/test-dump/Main.java11
-rw-r--r--tools/ahat/src/test/com/android/ahat/InstanceTest.java49
4 files changed, 101 insertions, 34 deletions
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 cb2d738f23..1a3d127fc9 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
@@ -428,8 +428,7 @@ public abstract class AhatInstance implements Diffable<AhatInstance>,
* 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) {
+ if (inst.isRoot()) {
return null;
}
return new PathElement(inst.mNextInstanceToGcRoot, inst.mNextInstanceToGcRootField);
@@ -487,40 +486,64 @@ public abstract class AhatInstance implements Diffable<AhatInstance>,
* mHardReverseReferences
* mSoftReverseReferences
*/
- static void computeReverseReferences(AhatInstance root) {
- // Do a breadth first search to visit the nodes.
- Queue<Reference> bfs = new ArrayDeque<Reference>();
+ static void computeReverseReferences(SuperRoot root) {
+ // Start by doing a breadth first search through strong references.
+ // Then continue the breadth first search through weak references.
+ Queue<Reference> strong = new ArrayDeque<Reference>();
+ Queue<Reference> weak = new ArrayDeque<Reference>();
+
for (Reference ref : root.getReferences()) {
- bfs.add(ref);
+ strong.add(ref);
}
- while (!bfs.isEmpty()) {
- Reference ref = bfs.poll();
- if (ref.ref.mHardReverseReferences == null && ref.strong) {
- // This is the first time we are seeing ref.ref through a strong
- // reference.
+ while (!strong.isEmpty()) {
+ Reference ref = strong.poll();
+ assert ref.strong;
+
+ if (ref.ref.mNextInstanceToGcRoot == null) {
+ // This is the first time we have seen ref.ref.
ref.ref.mNextInstanceToGcRoot = ref.src;
ref.ref.mNextInstanceToGcRootField = ref.field;
ref.ref.mHardReverseReferences = new ArrayList<AhatInstance>();
+
for (Reference childRef : ref.ref.getReferences()) {
- bfs.add(childRef);
+ if (childRef.strong) {
+ strong.add(childRef);
+ } else {
+ weak.add(childRef);
+ }
}
}
- // Note: ref.src is null when the src is the SuperRoot.
- if (ref.src != null) {
- if (ref.strong) {
- ref.ref.mHardReverseReferences.add(ref.src);
- } else {
- if (ref.ref.mSoftReverseReferences == null) {
- ref.ref.mSoftReverseReferences = new ArrayList<AhatInstance>();
- if (ref.ref.mNextInstanceToGcRoot == null) {
- ref.ref.mNextInstanceToGcRoot = ref.src;
- ref.ref.mNextInstanceToGcRootField = ref.field;
- }
- }
- ref.ref.mSoftReverseReferences.add(ref.src);
+ // 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.
+ 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);
+ }
+ }
+
+ 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);
}
}
}
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 a2adbd2808..5210e31167 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 @@ public class SuperRoot extends AhatInstance implements DominatorsComputation.Nod
@Override
public Reference get(int index) {
String field = ".roots[" + Integer.toString(index) + "]";
- return new Reference(null, field, mRoots.get(index), true);
+ return new Reference(SuperRoot.this, field, mRoots.get(index), true);
}
};
}
diff --git a/tools/ahat/src/test-dump/Main.java b/tools/ahat/src/test-dump/Main.java
index 333d28c214..079be7da81 100644
--- a/tools/ahat/src/test-dump/Main.java
+++ b/tools/ahat/src/test-dump/Main.java
@@ -93,6 +93,8 @@ public class Main {
null};
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 Object[] basicStringRef;
public AddedObject addedObject;
public UnchangedObject unchangedObject = new UnchangedObject();
@@ -126,10 +128,11 @@ public class Main {
Main.class.getClassLoader(), 0x12345, 50000);
registry.registerNativeAllocation(anObject, 0xABCDABCD);
- aLongStrongPathToSamplePathObject = new Reference(new Reference(new Object()));
- aShortWeakPathToSamplePathObject = new WeakReference(
- ((Reference)aLongStrongPathToSamplePathObject.referent).referent,
- referenceQueue);
+ {
+ Object object = new Object();
+ aLongStrongPathToSamplePathObject = new Reference(new Reference(new Reference(object)));
+ aShortWeakPathToSamplePathObject = new WeakReference(new Reference(object));
+ }
addedObject = baseline ? null : new AddedObject();
removedObject = baseline ? new RemovedObject() : null;
diff --git a/tools/ahat/src/test/com/android/ahat/InstanceTest.java b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
index a4908fd0ab..8fbb8849f0 100644
--- a/tools/ahat/src/test/com/android/ahat/InstanceTest.java
+++ b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
@@ -274,12 +274,20 @@ public class InstanceTest {
public void gcRootPathNotWeak() throws IOException {
TestDump dump = TestDump.getTestDump();
- AhatInstance strong = dump.getDumpedAhatInstance("aLongStrongPathToSamplePathObject");
- AhatInstance strong2 = strong.getField("referent").asAhatInstance();
- AhatInstance object = strong2.getField("referent").asAhatInstance();
+ // The test dump is set up to have the following graph:
+ // -S-> strong1 -S-> strong2 -S-> strong3 -S-> object
+ // -S-> weak1 -W-> weak2 ------------------S->-/
+ // The gc root path should go through the longer chain of strong
+ // references, not the shorter chain with weak references (even though the
+ // last element in the shorter chain is a strong reference).
+
+ AhatInstance strong1 = dump.getDumpedAhatInstance("aLongStrongPathToSamplePathObject");
+ AhatInstance strong2 = strong1.getField("referent").asAhatInstance();
+ AhatInstance strong3 = strong2.getField("referent").asAhatInstance();
+ AhatInstance object = strong3.getField("referent").asAhatInstance();
List<PathElement> path = object.getPathFromGcRoot();
- assertEquals(strong2, path.get(path.size() - 2).instance);
+ assertEquals(strong3, path.get(path.size() - 2).instance);
}
@Test
@@ -368,6 +376,39 @@ public class InstanceTest {
}
@Test
+ public void weakRefToGcRoot() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance ref = dump.getDumpedAhatInstance("aWeakRefToGcRoot");
+
+ // The weak reference points to Main.class, which we expect will be marked
+ // as a GC root. In theory Main.class doesn't have to be a GC root, in
+ // which case this test case will need to be revised.
+ AhatInstance root = ref.getField("referent").asAhatInstance();
+ assertTrue(root.isRoot());
+
+ // We had a bug in the past where weak references to GC roots caused the
+ // roots to be incorrectly be considered weakly reachable.
+ assertTrue(root.isStronglyReachable());
+ assertFalse(root.isWeaklyReachable());
+ }
+
+ @Test
+ public void weakReferenceChain() 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
+ // 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();
+ assertTrue(ref.isStronglyReachable());
+ assertTrue(weak1.isWeaklyReachable());
+ assertTrue(weak2.isWeaklyReachable());
+ assertTrue(weak3.isWeaklyReachable());
+ }
+
+ @Test
public void reverseReferences() throws IOException {
TestDump dump = TestDump.getTestDump();
AhatInstance obj = dump.getDumpedAhatInstance("anObject");