Skip to content

Commit eccba7a

Browse files
authored
Merge pull request #480 from square/py/find_tracked_references
Add API for finding the list of tracked references.
2 parents 535eb19 + 633c37d commit eccba7a

File tree

5 files changed

+151
-30
lines changed

5 files changed

+151
-30
lines changed

leakcanary-analyzer/src/main/java/com/squareup/leakcanary/HeapAnalyzer.java

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import static com.squareup.leakcanary.HahaHelper.extendsThread;
4444
import static com.squareup.leakcanary.HahaHelper.fieldToString;
4545
import static com.squareup.leakcanary.HahaHelper.fieldValue;
46+
import static com.squareup.leakcanary.HahaHelper.hasField;
4647
import static com.squareup.leakcanary.HahaHelper.threadName;
4748
import static com.squareup.leakcanary.LeakTraceElement.Holder.ARRAY;
4849
import static com.squareup.leakcanary.LeakTraceElement.Holder.CLASS;
@@ -63,6 +64,36 @@ public HeapAnalyzer(ExcludedRefs excludedRefs) {
6364
this.excludedRefs = excludedRefs;
6465
}
6566

67+
public List<TrackedReference> findTrackedReferences(File heapDumpFile) {
68+
if (!heapDumpFile.exists()) {
69+
throw new IllegalArgumentException("File does not exist: " + heapDumpFile);
70+
}
71+
try {
72+
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
73+
HprofParser parser = new HprofParser(buffer);
74+
Snapshot snapshot = parser.parse();
75+
deduplicateGcRoots(snapshot);
76+
77+
ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
78+
List<TrackedReference> references = new ArrayList<>();
79+
for (Instance weakRef : refClass.getInstancesList()) {
80+
List<ClassInstance.FieldValue> values = classInstanceValues(weakRef);
81+
String key = asString(fieldValue(values, "key"));
82+
String name =
83+
hasField(values, "name") ? asString(fieldValue(values, "name")) : "(No name field)";
84+
Instance instance = fieldValue(values, "referent");
85+
if (instance != null) {
86+
String className = getClassName(instance);
87+
List<String> fields = describeFields(instance);
88+
references.add(new TrackedReference(key, name, className, fields));
89+
}
90+
}
91+
return references;
92+
} catch (Throwable e) {
93+
throw new RuntimeException(e);
94+
}
95+
}
96+
6697
/**
6798
* Searches the heap dump for a {@link KeyedWeakReference} instance with the corresponding key,
6899
* and then computes the shortest strong reference path from that instance to the GC roots.
@@ -112,8 +143,7 @@ void deduplicateGcRoots(Snapshot snapshot) {
112143
// Repopulate snapshot with unique GC roots.
113144
gcRoots.clear();
114145
uniqueRootMap.forEach(new TObjectProcedure<String>() {
115-
@Override
116-
public boolean execute(String key) {
146+
@Override public boolean execute(String key) {
117147
return gcRoots.add(uniqueRootMap.get(key));
118148
}
119149
});
@@ -250,37 +280,16 @@ private LeakTraceElement buildLeakElement(LeakNode node) {
250280
LeakTraceElement.Holder holderType;
251281
String className;
252282
String extra = null;
253-
List<String> fields = new ArrayList<>();
283+
List<String> fields = describeFields(holder);
284+
285+
className = getClassName(holder);
286+
254287
if (holder instanceof ClassObj) {
255-
ClassObj classObj = (ClassObj) holder;
256288
holderType = CLASS;
257-
className = classObj.getClassName();
258-
for (Map.Entry<Field, Object> entry : classObj.getStaticFieldValues().entrySet()) {
259-
Field field = entry.getKey();
260-
Object value = entry.getValue();
261-
fields.add("static " + field.getName() + " = " + value);
262-
}
263289
} else if (holder instanceof ArrayInstance) {
264-
ArrayInstance arrayInstance = (ArrayInstance) holder;
265290
holderType = ARRAY;
266-
className = arrayInstance.getClassObj().getClassName();
267-
if (arrayInstance.getArrayType() == Type.OBJECT) {
268-
Object[] values = arrayInstance.getValues();
269-
for (int i = 0; i < values.length; i++) {
270-
fields.add("[" + i + "] = " + values[i]);
271-
}
272-
}
273291
} else {
274-
ClassInstance classInstance = (ClassInstance) holder;
275292
ClassObj classObj = holder.getClassObj();
276-
for (Map.Entry<Field, Object> entry : classObj.getStaticFieldValues().entrySet()) {
277-
fields.add("static " + fieldToString(entry));
278-
}
279-
for (ClassInstance.FieldValue field : classInstance.getValues()) {
280-
fields.add(fieldToString(field));
281-
}
282-
className = classObj.getClassName();
283-
284293
if (extendsThread(classObj)) {
285294
holderType = THREAD;
286295
String threadName = threadName(holder);
@@ -316,6 +325,52 @@ private LeakTraceElement buildLeakElement(LeakNode node) {
316325
fields);
317326
}
318327

328+
private List<String> describeFields(Instance instance) {
329+
List<String> fields = new ArrayList<>();
330+
331+
if (instance instanceof ClassObj) {
332+
ClassObj classObj = (ClassObj) instance;
333+
for (Map.Entry<Field, Object> entry : classObj.getStaticFieldValues().entrySet()) {
334+
Field field = entry.getKey();
335+
Object value = entry.getValue();
336+
fields.add("static " + field.getName() + " = " + value);
337+
}
338+
} else if (instance instanceof ArrayInstance) {
339+
ArrayInstance arrayInstance = (ArrayInstance) instance;
340+
if (arrayInstance.getArrayType() == Type.OBJECT) {
341+
Object[] values = arrayInstance.getValues();
342+
for (int i = 0; i < values.length; i++) {
343+
fields.add("[" + i + "] = " + values[i]);
344+
}
345+
}
346+
} else {
347+
ClassObj classObj = instance.getClassObj();
348+
for (Map.Entry<Field, Object> entry : classObj.getStaticFieldValues().entrySet()) {
349+
fields.add("static " + fieldToString(entry));
350+
}
351+
ClassInstance classInstance = (ClassInstance) instance;
352+
for (ClassInstance.FieldValue field : classInstance.getValues()) {
353+
fields.add(fieldToString(field));
354+
}
355+
}
356+
return fields;
357+
}
358+
359+
private String getClassName(Instance instance) {
360+
String className;
361+
if (instance instanceof ClassObj) {
362+
ClassObj classObj = (ClassObj) instance;
363+
className = classObj.getClassName();
364+
} else if (instance instanceof ArrayInstance) {
365+
ArrayInstance arrayInstance = (ArrayInstance) instance;
366+
className = arrayInstance.getClassObj().getClassName();
367+
} else {
368+
ClassObj classObj = instance.getClassObj();
369+
className = classObj.getClassName();
370+
}
371+
return className;
372+
}
373+
319374
private long since(long analysisStartNanoTime) {
320375
return NANOSECONDS.toMillis(System.nanoTime() - analysisStartNanoTime);
321376
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.squareup.leakcanary;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collections;
5+
import java.util.List;
6+
7+
/**
8+
* An instance tracked by a {@link KeyedWeakReference} that hadn't been cleared when the
9+
* heap was dumped. May or may not point to a leaking reference.
10+
*/
11+
public class TrackedReference {
12+
13+
/** Corresponds to {@link KeyedWeakReference#key}. */
14+
public final String key;
15+
16+
/** Corresponds to {@link KeyedWeakReference#name}. */
17+
public final String name;
18+
19+
/** Class of the tracked instance. */
20+
public final String className;
21+
22+
/** List of all fields (member and static) for that instance. */
23+
public final List<String> fields;
24+
25+
public TrackedReference(String key, String name, String className, List<String> fields) {
26+
this.key = key;
27+
this.name = name;
28+
this.className = className;
29+
this.fields = Collections.unmodifiableList(new ArrayList<>(fields));
30+
}
31+
}

leakcanary-analyzer/src/test/java/com/squareup/leakcanary/HeapAnalyzerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313

1414
import static com.squareup.haha.perflib.RootType.NATIVE_STATIC;
1515
import static com.squareup.haha.perflib.RootType.SYSTEM_CLASS;
16+
import static com.squareup.leakcanary.TestUtil.NO_EXCLUDED_REFS;
1617
import static java.util.Arrays.asList;
1718
import static org.assertj.core.api.Assertions.assertThat;
1819

1920
public class HeapAnalyzerTest {
20-
private static final ExcludedRefs NO_EXCLUDED_REFS = null;
2121
private static final List<RootObj> DUP_ROOTS =
2222
asList(new RootObj(SYSTEM_CLASS, 6L),
2323
new RootObj(SYSTEM_CLASS, 5L),

leakcanary-analyzer/src/test/java/com/squareup/leakcanary/TestUtil.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,20 @@
1717

1818
import java.io.File;
1919
import java.net.URL;
20+
import java.util.List;
2021

2122
final class TestUtil {
2223

24+
public static final ExcludedRefs NO_EXCLUDED_REFS = ExcludedRefs.builder().build();
25+
2326
enum HeapDumpFile {
2427
ASYNC_TASK("leak_asynctask.hprof", "dc983a12-d029-4003-8890-7dd644c664c5"),
2528
ASYNC_TASK_MPREVIEW2("leak_asynctask_mpreview2.hprof", "1114018e-e154-435f-9a3d-da63ae9b47fa"),
2629
ASYNC_TASK_M_POSTPREVIEW2("leak_asynctask_m_postpreview2.hprof",
2730
"25ae1778-7c1d-4ec7-ac50-5cce55424069");
2831

29-
private final String filename;
30-
private final String referenceKey;
32+
public final String filename;
33+
public final String referenceKey;
3134

3235
HeapDumpFile(String filename, String referenceKey) {
3336
this.filename = filename;
@@ -42,6 +45,12 @@ static File fileFromName(String filename) {
4245
return new File(url.getPath());
4346
}
4447

48+
static List<TrackedReference> findTrackedReferences(HeapDumpFile heapDumpFile) {
49+
File file = fileFromName(heapDumpFile.filename);
50+
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(NO_EXCLUDED_REFS);
51+
return heapAnalyzer.findTrackedReferences(file);
52+
}
53+
4554
static AnalysisResult analyze(HeapDumpFile heapDumpFile, ExcludedRefs.BuilderWithParams excludedRefs) {
4655
File file = fileFromName(heapDumpFile.filename);
4756
String referenceKey = heapDumpFile.referenceKey;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.squareup.leakcanary;
2+
3+
import java.util.List;
4+
import org.junit.Test;
5+
6+
import static com.squareup.leakcanary.TestUtil.HeapDumpFile.ASYNC_TASK;
7+
import static com.squareup.leakcanary.TestUtil.HeapDumpFile.ASYNC_TASK_MPREVIEW2;
8+
import static com.squareup.leakcanary.TestUtil.HeapDumpFile.ASYNC_TASK_M_POSTPREVIEW2;
9+
import static com.squareup.leakcanary.TestUtil.findTrackedReferences;
10+
import static org.assertj.core.api.Assertions.assertThat;
11+
12+
public class TrackedReferencesTest {
13+
14+
@Test public void findsExpectedRef() {
15+
List<TrackedReference> trackedReferences = findTrackedReferences(ASYNC_TASK_M_POSTPREVIEW2);
16+
assertThat(trackedReferences).hasSize(1);
17+
TrackedReference firstRef = trackedReferences.get(0);
18+
assertThat(firstRef.key).isEqualTo(ASYNC_TASK_M_POSTPREVIEW2.referenceKey);
19+
assertThat(firstRef.className).isEqualTo("com.example.leakcanary.MainActivity");
20+
}
21+
22+
@Test public void findsSeveralRefs() {
23+
List<TrackedReference> trackedReferences = findTrackedReferences(ASYNC_TASK);
24+
assertThat(trackedReferences).hasSize(2);
25+
}
26+
}

0 commit comments

Comments
 (0)