diff options
| -rw-r--r-- | tools/ahat/etc/test-dump-base.hprof | bin | 1802095 -> 1777909 bytes | |||
| -rw-r--r-- | tools/ahat/etc/test-dump.hprof | bin | 2695590 -> 2671386 bytes | |||
| -rw-r--r-- | tools/ahat/etc/test-dump.map | 66 | ||||
| -rw-r--r-- | tools/ahat/src/main/com/android/ahat/ObjectHandler.java | 16 | ||||
| -rw-r--r-- | tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java | 56 | ||||
| -rw-r--r-- | tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java | 6 | ||||
| -rw-r--r-- | tools/ahat/src/test-dump/DumpedStuff.java | 14 | ||||
| -rw-r--r-- | tools/ahat/src/test/com/android/ahat/ObjectHandlerTest.java | 38 |
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 Binary files differindex d0c3f342c2..87049c2ec6 100644 --- a/tools/ahat/etc/test-dump-base.hprof +++ b/tools/ahat/etc/test-dump-base.hprof diff --git a/tools/ahat/etc/test-dump.hprof b/tools/ahat/etc/test-dump.hprof Binary files differindex 84ad4287e5..e539e16e00 100644 --- a/tools/ahat/etc/test-dump.hprof +++ b/tools/ahat/etc/test-dump.hprof 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()); + } } |