summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tools/ahat/etc/test-dump-base.hprofbin1802095 -> 1777909 bytes
-rw-r--r--tools/ahat/etc/test-dump.hprofbin2695590 -> 2671386 bytes
-rw-r--r--tools/ahat/etc/test-dump.map66
-rw-r--r--tools/ahat/src/main/com/android/ahat/ObjectHandler.java16
-rw-r--r--tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java56
-rw-r--r--tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java6
-rw-r--r--tools/ahat/src/test-dump/DumpedStuff.java14
-rw-r--r--tools/ahat/src/test/com/android/ahat/ObjectHandlerTest.java38
8 files changed, 147 insertions, 49 deletions
diff --git a/tools/ahat/etc/test-dump-base.hprof b/tools/ahat/etc/test-dump-base.hprof
index d0c3f342c2..87049c2ec6 100644
--- a/tools/ahat/etc/test-dump-base.hprof
+++ b/tools/ahat/etc/test-dump-base.hprof
Binary files differ
diff --git a/tools/ahat/etc/test-dump.hprof b/tools/ahat/etc/test-dump.hprof
index 84ad4287e5..e539e16e00 100644
--- a/tools/ahat/etc/test-dump.hprof
+++ b/tools/ahat/etc/test-dump.hprof
Binary files differ
diff --git a/tools/ahat/etc/test-dump.map b/tools/ahat/etc/test-dump.map
index 7d5ad035b2..c956de19e8 100644
--- a/tools/ahat/etc/test-dump.map
+++ b/tools/ahat/etc/test-dump.map
@@ -1,11 +1,11 @@
# compiler: R8
-# compiler_version: 8.9.19-dev
+# compiler_version: 8.10.9-dev
# min_api: 35
-# compiler_hash: b6b2b638738ed9914c3cbc7103ae1a236354c1b4
+# compiler_hash: a7ad18a70460b799d0482e497c109a75bf7f91de
# common_typos_disable
# {"id":"com.android.tools.r8.mapping","version":"2.2"}
-# pg_map_id: e381cdb92198880e7b46db4aaea8369ad60617c62144964c6424590112d04f37
-# pg_map_hash: SHA-256 e381cdb92198880e7b46db4aaea8369ad60617c62144964c6424590112d04f37
+# pg_map_id: edbbcc48c7b01d8c2f908b650b78ba301895bb3a321ccf9468aaf6b721e19d39
+# pg_map_hash: SHA-256 edbbcc48c7b01d8c2f908b650b78ba301895bb3a321ccf9468aaf6b721e19d39
DumpedStuff -> DumpedStuff:
# {"id":"sourceFile","fileName":"DumpedStuff.java"}
java.lang.ref.WeakReference aShortWeakPathToSamplePathObject -> A
@@ -40,7 +40,8 @@ DumpedStuff -> DumpedStuff:
java.lang.Object fakeBinderService -> U
java.lang.Object binderToken -> V
java.lang.Object namedBinderToken -> W
- java.lang.String modifiedStaticField -> X
+ java.lang.Object unreachableAnchor -> X
+ java.lang.String modifiedStaticField -> Y
java.lang.String basicString -> d
java.lang.String nonAscii -> e
java.lang.String embeddedZero -> f
@@ -70,26 +71,27 @@ DumpedStuff -> DumpedStuff:
# {"id":"com.android.tools.r8.residualsignature","signature":"[Ll;"}
DumpedStuff$Reference aLongStrongPathToSamplePathObject -> z
# {"id":"com.android.tools.r8.residualsignature","signature":"Lm;"}
- 1:6:void <clinit>():242:242 -> <clinit>
+ 1:6:void <clinit>():256:256 -> <clinit>
1:1:void <init>(boolean):45:45 -> <init>
- 2:11:void <init>(boolean):177:186 -> <init>
- 12:17:void <init>(boolean):188:193 -> <init>
- 18:20:void <init>(boolean):196:198 -> <init>
- 21:22:void <init>(boolean):205:206 -> <init>
- 23:23:void <init>(boolean):209:209 -> <init>
- 24:29:void <init>(boolean):218:223 -> <init>
- 30:33:void <init>(boolean):225:228 -> <init>
- 34:34:void <init>(boolean):46:46 -> <init>
- 35:35:void <init>(boolean):49:49 -> <init>
- 36:36:void <init>(boolean):51:51 -> <init>
- 37:39:void <init>(boolean):55:57 -> <init>
- 40:41:void <init>(boolean):60:61 -> <init>
- 42:44:void <init>(boolean):65:67 -> <init>
- 45:52:void <init>(boolean):70:77 -> <init>
- 53:55:void <init>(boolean):82:84 -> <init>
- 56:58:void <init>(boolean):87:89 -> <init>
- 59:59:void <init>(boolean):93:93 -> <init>
- 60:61:void <init>(boolean):95:96 -> <init>
+ 2:11:void <init>(boolean):187:196 -> <init>
+ 12:17:void <init>(boolean):198:203 -> <init>
+ 18:20:void <init>(boolean):206:208 -> <init>
+ 21:22:void <init>(boolean):215:216 -> <init>
+ 23:23:void <init>(boolean):219:219 -> <init>
+ 24:29:void <init>(boolean):228:233 -> <init>
+ 30:33:void <init>(boolean):235:238 -> <init>
+ 34:34:void <init>(boolean):240:240 -> <init>
+ 35:35:void <init>(boolean):46:46 -> <init>
+ 36:36:void <init>(boolean):49:49 -> <init>
+ 37:37:void <init>(boolean):51:51 -> <init>
+ 38:40:void <init>(boolean):55:57 -> <init>
+ 41:42:void <init>(boolean):60:61 -> <init>
+ 43:45:void <init>(boolean):65:67 -> <init>
+ 46:53:void <init>(boolean):70:77 -> <init>
+ 54:56:void <init>(boolean):82:84 -> <init>
+ 57:59:void <init>(boolean):87:89 -> <init>
+ 60:60:void <init>(boolean):93:93 -> <init>
+ 61:62:void <init>(boolean):95:96 -> <init>
1:8:void allocateObjectAtOverriddenSite():42:42 -> b
1:7:void allocateObjectAtKnownSite():30:30 -> c
8:10:void allocateObjectAtKnownSite():31:31 -> c
@@ -97,7 +99,8 @@ DumpedStuff -> DumpedStuff:
14:16:void allocateObjectAtKnownSite():33:33 -> c
17:20:void allocateObjectAtKnownSite():34:34 -> c
1:8:void allocateObjectAtKnownSubSite():38:38 -> d
- 1:40:void shouldNotGc():233:233 -> e
+ 1:39:void shouldNotGc():245:245 -> e
+ 40:47:void shouldNotGc():252:252 -> e
DumpedStuff$AddedObject -> a:
# {"id":"sourceFile","fileName":"DumpedStuff.java"}
1:4:void <init>():109:109 -> <init>
@@ -112,13 +115,13 @@ DumpedStuff$BinderService -> c:
# {"id":"sourceFile","fileName":"DumpedStuff.java"}
1:1:void <init>(DumpedStuff-IA):0:0 -> <init>
# {"id":"com.android.tools.r8.synthesized"}
- # {"id":"com.android.tools.r8.residualsignature","signature":"(Lq;)V"}
+ # {"id":"com.android.tools.r8.residualsignature","signature":"(Lr;)V"}
2:2:void <init>():169:169 -> <init>
DumpedStuff$FakeBinderService -> d:
# {"id":"sourceFile","fileName":"DumpedStuff.java"}
1:1:void <init>(DumpedStuff-IA):0:0 -> <init>
# {"id":"com.android.tools.r8.synthesized"}
- # {"id":"com.android.tools.r8.residualsignature","signature":"(Lq;)V"}
+ # {"id":"com.android.tools.r8.residualsignature","signature":"(Lr;)V"}
2:2:void <init>():173:173 -> <init>
DumpedStuff$IBinderInterfaceImpostor -> g:
# {"id":"sourceFile","fileName":"DumpedStuff.java"}
@@ -178,7 +181,14 @@ DumpedStuff$StackSmasher -> o:
DumpedStuff$UnchangedObject -> p:
# {"id":"sourceFile","fileName":"DumpedStuff.java"}
1:4:void <init>():115:115 -> <init>
-DumpedStuff-IA -> q:
+DumpedStuff$Unreachable -> q:
+# {"id":"sourceFile","fileName":"DumpedStuff.java"}
+ java.lang.Object anchor -> a
+ java.lang.Object self -> b
+ 1:3:void <init>(java.lang.Object):181:181 -> <init>
+ 4:5:void <init>(java.lang.Object):182:182 -> <init>
+ 6:8:void <init>(java.lang.Object):183:183 -> <init>
+DumpedStuff-IA -> r:
# {"id":"sourceFile","fileName":"R8$$SyntheticClass"}
# {"id":"com.android.tools.r8.synthesized"}
Main -> Main:
diff --git a/tools/ahat/src/main/com/android/ahat/ObjectHandler.java b/tools/ahat/src/main/com/android/ahat/ObjectHandler.java
index b311edce12..c8677cf40b 100644
--- a/tools/ahat/src/main/com/android/ahat/ObjectHandler.java
+++ b/tools/ahat/src/main/com/android/ahat/ObjectHandler.java
@@ -67,9 +67,7 @@ class ObjectHandler implements AhatHandler {
printAllocationSite(doc, query, inst);
- if (!inst.isUnreachable()) {
- printGcRootPath(doc, query, inst);
- }
+ printSamplePath(doc, query, inst);
doc.section("Object Info");
AhatClassObj cls = inst.getClassObj();
@@ -258,13 +256,17 @@ class ObjectHandler implements AhatHandler {
}
}
- private void printGcRootPath(Doc doc, Query query, AhatInstance inst) {
- doc.section("Sample Path from GC Root");
- List<PathElement> path = inst.getPathFromGcRoot();
+ private void printSamplePath(Doc doc, Query query, AhatInstance inst) {
+ List<PathElement> path = inst.getSamplePath();
// Add a fake PathElement as a marker for the root.
final PathElement root = new PathElement(null, null);
- path.add(0, root);
+ if (inst.isUnreachable()) {
+ doc.section("Sample Path");
+ } else {
+ doc.section("Sample Path from GC Root");
+ path.add(0, root);
+ }
HeapTable.TableConfig<PathElement> table = new HeapTable.TableConfig<PathElement>() {
public String getHeapsDescription() {
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 edc71d48bc..e42453e41f 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
@@ -616,10 +616,10 @@ public abstract class AhatInstance implements Diffable<AhatInstance> {
/**
* 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.
+ * of the returned path is a GC root object. The last element of the
+ * returned path is 'this' with an empty field description.
* <p>
- * If the instance is strongly reachable, a path of string references will
+ * If the instance is strongly reachable, a path of strong 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.
@@ -631,7 +631,18 @@ public abstract class AhatInstance implements Diffable<AhatInstance> {
if (isUnreachable()) {
return null;
}
+ return getSamplePath();
+ }
+ /**
+ * Returns a sample path to this instance.
+ * If the instance is reachable, this returns a path from a GC root.
+ * Otherwise this returns an arbitrary path leading to the instance.
+ *
+ * @return sample path to this instance
+ * @see PathElement
+ */
+ public List<PathElement> getSamplePath() {
List<PathElement> path = new ArrayList<PathElement>();
AhatInstance dom = this;
@@ -653,7 +664,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) {
- if (inst.isRoot()) {
+ if (inst.isRoot() || inst.mNextInstanceToGcRoot == null) {
return null;
}
return new PathElement(inst.mNextInstanceToGcRoot, inst.mNextInstanceToGcRootField);
@@ -714,19 +725,20 @@ public abstract class AhatInstance implements Diffable<AhatInstance> {
}
/**
- * Determine the reachability of the all instances reachable from the given
- * root instance. Initializes the following fields:
+ * Determine the reachability of instances.
+ * Initializes the following fields:
* mReachability
* mNextInstanceToGcRoot
* mNextInstanceToGcRootField
* mReverseReferences
*
+ * @param root root used for determining which instances are reachable.
+ * @param insts the list of all instances.
* @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.
+ * @param numInsts the number of instances, for tracking progress.
*/
- static void computeReachability(SuperRoot root, Progress progress, long numInsts) {
+ static void computeReachability(
+ SuperRoot root, Iterable<AhatInstance> insts, 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);
@@ -768,6 +780,30 @@ public abstract class AhatInstance implements Diffable<AhatInstance> {
}
}
}
+
+ // Initialize reachability related fields for unreachable instances,
+ // just in case people want to explore more about where unreachable
+ // instances come from.
+ for (AhatInstance inst : insts) {
+ if (inst.isUnreachable()) {
+ progress.advance();
+ for (Reference ref : inst.getReferences()) {
+ if (ref.ref.mReverseReferences == null) {
+ ref.ref.mReverseReferences = new ArrayList<AhatInstance>();
+ }
+ ref.ref.mReverseReferences.add(ref.src);
+
+ // An unreachable instance doesn't have a path to GC root, but it's
+ // still useful to see a sample path of who is referencing the
+ // object. To avoid introducing cycles in the sample path, we force
+ // the sample paths to have objects in increasing id order.
+ if (ref.ref.mNextInstanceToGcRoot == null && ref.src.mId < ref.ref.mId) {
+ ref.ref.mNextInstanceToGcRoot = ref.src;
+ ref.ref.mNextInstanceToGcRootField = ref.field;
+ }
+ }
+ }
+ }
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 1c36448337..63b3774b9a 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java
@@ -50,7 +50,7 @@ public class AhatSnapshot implements Diffable<AhatSnapshot> {
mHeaps = heaps;
mRootSite = rootSite;
- AhatInstance.computeReachability(mSuperRoot, progress, mInstances.size());
+ AhatInstance.computeReachability(mSuperRoot, mInstances, progress, mInstances.size());
mBitmapDumpData = AhatBitmapInstance.findBitmapDumpData(mSuperRoot, mInstances);
@@ -65,7 +65,9 @@ public class AhatSnapshot implements Diffable<AhatSnapshot> {
}
if (retained == Reachability.UNREACHABLE && inst.isUnreachable()) {
- mSuperRoot.addRoot(inst);
+ if (inst.getSamplePath().size() == 1) {
+ mSuperRoot.addRoot(inst);
+ }
}
}
diff --git a/tools/ahat/src/test-dump/DumpedStuff.java b/tools/ahat/src/test-dump/DumpedStuff.java
index e1f97fa895..19f6524122 100644
--- a/tools/ahat/src/test-dump/DumpedStuff.java
+++ b/tools/ahat/src/test-dump/DumpedStuff.java
@@ -174,6 +174,16 @@ public class DumpedStuff extends SuperDumpedStuff {
// Intentionally empty
};
+ private static class Unreachable {
+ public Object anchor;
+ public Object self;
+
+ public Unreachable(Object anchor) {
+ this.self = this;
+ this.anchor = anchor;
+ }
+ }
+
public String basicString = "hello, world";
public String nonAscii = "Sigma (Ʃ) is not ASCII";
public String embeddedZero = "embedded\0..."; // Non-ASCII for string compression purposes.
@@ -227,6 +237,8 @@ public class DumpedStuff extends SuperDumpedStuff {
Object binderToken = new android.os.Binder();
Object namedBinderToken = new android.os.Binder("awesomeToken");
+ Object unreachableAnchor = new Object();
+
// Allocate those objects that we need to not be GC'd before taking the heap
// dump.
public void shouldNotGc() {
@@ -236,6 +248,8 @@ public class DumpedStuff extends SuperDumpedStuff {
new WeakReference(
new SoftReference(
new PhantomReference(new Object(), referenceQueue))))));
+
+ new Unreachable(unreachableAnchor);
}
static {
diff --git a/tools/ahat/src/test/com/android/ahat/ObjectHandlerTest.java b/tools/ahat/src/test/com/android/ahat/ObjectHandlerTest.java
index 1b8a781e0c..b29f9716bc 100644
--- a/tools/ahat/src/test/com/android/ahat/ObjectHandlerTest.java
+++ b/tools/ahat/src/test/com/android/ahat/ObjectHandlerTest.java
@@ -16,12 +16,19 @@
package com.android.ahat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
import com.android.ahat.heapdump.AhatInstance;
import com.android.ahat.heapdump.AhatSnapshot;
-import java.io.IOException;
+import com.android.ahat.heapdump.Site;
+
import org.junit.Test;
-import static org.junit.Assert.assertNotNull;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
public class ObjectHandlerTest {
@Test
@@ -71,4 +78,31 @@ public class ObjectHandlerTest {
AhatHandler handler = new ObjectHandler(dump.getAhatSnapshot());
TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + object.getId());
}
+
+ @Test
+ public void noCrashUnreachable() throws IOException {
+ // Exercise the case where the object is unreachable.
+ // We had bugs in the past where trying to print the sample path for any
+ // unreachable instance would lead to an infinite loop or null pointer
+ // exception.
+
+ // Our unreachable object should be the only reference to the
+ // unreachableAnchor instance, aside from dumpedStuff.
+ TestDump dump = TestDump.getTestDump();
+
+ AhatInstance anchor = dump.getDumpedAhatInstance("unreachableAnchor");
+ assertNotNull(anchor);
+
+ List<AhatInstance> reverse = anchor.getReverseReferences();
+ assertEquals(2, reverse.size());
+
+ AhatInstance unreachable = reverse.get(0);
+ if (!unreachable.isUnreachable()) {
+ unreachable = reverse.get(1);
+ }
+ assertTrue(unreachable.isUnreachable());
+
+ AhatHandler handler = new ObjectHandler(dump.getAhatSnapshot());
+ TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + unreachable.getId());
+ }
}