1、LeakCanary的使用
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
}
LeakCanary.install(this)是分析的入口
2、LeakCanary.install(Application application)
public static RefWatcher install(Application application) {
//refWatcher返回AndroidRefWatcherBuilder對象
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) //excludedRefs 排除不希望檢測的對象
.buildAndInstall(); //見3
}
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}
創建了AndroidRefWatcherBuilder對象,接着調用了buildAndInstall方法
3、AndroidRefWatcherBuilder.buildAndInstall()
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();// 見4 調了父類RefWatcherBuilder的build方法
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);//見5
}
return refWatcher;
}
創建出RefWatcher對象,接着ActivityRefWatcher去installl
4、RefWatcherBuilder.build()
public final RefWatcher build() {
if (isDisabled()) {
return RefWatcher.DISABLED;
}
//下面是構建RefWatcher對象所需參數
ExcludedRefs excludedRefs = this.excludedRefs;
if (excludedRefs == null) {
excludedRefs = defaultExcludedRefs();
}
HeapDump.Listener heapDumpListener = this.heapDumpListener;
if (heapDumpListener == null) {
heapDumpListener = defaultHeapDumpListener();
}
DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
debuggerControl = defaultDebuggerControl();
}
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
heapDumper = defaultHeapDumper();
}
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
watchExecutor = defaultWatchExecutor();
}
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
//返回新建的RefWatcher實例
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
excludedRefs);
}
構建了RefWatcher對象。顧名思義,RefWatcher對象是用來觀察引用的
5、ActivityRefWatcher.install()
public static void install(Application application, RefWatcher refWatcher) {
//創建ActivityRefWatcher用於檢測Activity
new ActivityRefWatcher(application, refWatcher).watchActivities();
}
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
//註冊activity生命週期回調
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
...
@Override public void onActivityDestroyed(Activity activity) {
//在Activity.onDestoryed回調時,運行ActivityRefWatcher.onActivityDestroyed(activity)方法
ActivityRefWatcher.this.onActivityDestroyed(activity);//見6
}
};
在Activity.onDestoryed回調時,運行ActivityRefWatcher.onActivityDestroyed(activity)方法
6、ActivityRefWatcher.onActivityDestroyed(activity)
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);//見7
}
調用了refWatcher.watch()方法,refWatcher就是第5小結構建ActivityRefWatcher對象構建方法傳入的
7、RefWatcher.watch()
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString(); //生產和被觀察關聯的key
retainedKeys.add(key);//retainedKeys保存key
//創建KeyedWeakReference,它是WeakReference的子類,增加了key和name屬性。
//把檢測對象和ReferenQueue對象也傳了進去
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);//見8
}
這裏注意下,RefWatcher.watch()接收到的是Object對象,也就是說LeakCanary本質上是支持所有對象泄漏檢測的,在檢測的地方運行RefWatcher.watch()方法即可。
8、RefWatcher.ensureGoneAsync()
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);//見9
}
});
}
watchExecutor是一個封裝了Handler、具有重試功能的執行器。傳遞給WatchExecutor.execute方法的Retryable對象,在經過5秒延遲後會執行run方法。(5秒延遲是跟ANR有關???),run方法調了ensureGone方法
9、RefWatcher.ensureGone()
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();//見10 清除入隊的弱引用
if (debuggerControl.isDebuggerAttached()) {
// debug模式下重試.
return RETRY;
}
//gone方法如果檢測到對象被回收,說明沒有泄露
if (gone(reference)) {//見10
return DONE;
}
//觸發GC
gcTrigger.runGc();
removeWeaklyReachableReferences();//清除入隊的弱引用
if (!gone(reference)) {//再次判斷是否檢測對象被回收
//仍然沒有正常回收檢測對象
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//dump出內存信息
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
//構建HeapDump去分析內存泄露的詳細信息
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));//見11
}
return DONE;
}
首先運行removeWeaklyReachableReferences清除入隊的弱引用,然後gone方法判斷檢測對象是否被回收。若未被回收觸發一次gc,接着再清除入隊的弱引用。如果仍未被回收就dump出hprof文件去分析。
看下removeWeaklyReachableReferences和gone方法的實現
10、RefWatcher.gone() & removeWeaklyReachableReferences()
private void removeWeaklyReachableReferences() {
KeyedWeakReference ref;
//移除入隊的Reference
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key); //retainedKeys刪除關聯的key
}
}
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
從queue逐個移除KeyedWeakReference對象,接着移除retainedKeys中和KeyedWeakReference對象關聯的key,也就是如果檢測對象已經到達過引用隊列,被正常回收,retainedKeys是不再包含關聯的key。所以gone方法可以通過判斷retainedKeys是否包含key來確認被檢測對像是否被回收。
11、ServiceHeapDumpListener.analyze()
heapdumpListener.analyze會走到HeapAnalyzerService.onHandleIntent()方法
protected void onHandleIntent(Intent intent) {
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
//創建分析類
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
//見12 分析流程
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
//分析後
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
重點的一步調了heapAnalyzer.checkForLeak分析
12、HeapAnalyzer.checkForLeak()
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
long analysisStartNanoTime = System.nanoTime();
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
//MemoryMappedFileBuffer封裝heapDumpFile
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
//HprofParser分析hprof文件
HprofParser parser = new HprofParser(buffer);
//分析結果封裝在Snapshot對象中
Snapshot snapshot = parser.parse();//見13
//去重GcRoot
deduplicateGcRoots(snapshot);
//通過key找到檢測對象
Instance leakingRef = findLeakingReference(referenceKey, snapshot);//見14
// 沒有泄露
if (leakingRef == null) {
return noLeak(since(analysisStartNanoTime));
}
//找到最短泄露路徑
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef); //見15
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
主要有幾個步驟:
- 把heapDumpFile封裝成MemoryMappedFileBuffer。
- 通過HprofParser對象去分析MemoryMappedFileBuffer。
- 去除Snapshot中重複的GcRoot。
- 通過key找到Snapshot中保存的泄露對象。
- 分析GcRoot到泄露對象的最短路徑。
checkForLeak方法有兩個東西的沒搞懂,一個Snapshot的數據結構,另一個是findLeakingReference方法。先看看Snapshot的數據結構。
下面信息根據debug追蹤、註釋、類屬性和命名等方式得出的結果,未必正確,希望有大牛指點一二
13、Snapshot 數據結構
/*
* A snapshot of all of the heaps, and related meta-data, for the runtime at a given instant.
*
* There are three possible heaps: default, app and zygote. GC roots are always reported in the
* default heap, and they are simply references to objects living in the zygote or the app heap.
* During parsing of the HPROF file HEAP_DUMP_INFO chunks change which heap is being referenced.
*/
public class Snapshot {
private static final String JAVA_LANG_CLASS = "java.lang.Class";
// Special root object used in dominator computation for objects reachable via multiple roots.
public static final Instance SENTINEL_ROOT = new RootObj(RootType.UNKNOWN);
private static final int DEFAULT_HEAP_ID = 0;
final HprofBuffer mBuffer;
//堆信息
ArrayList<Heap> mHeaps = new ArrayList<Heap>();
Heap mCurrentHeap;
private ImmutableList<Instance> mTopSort;
private Dominators mDominators;
// 保存各種Reference以及它的子類的Class信息
private THashSet<ClassObj> mReferenceClasses = new THashSet<ClassObj>();
private int[] mTypeSizes;
private long mIdSizeMask = 0x00000000ffffffffl;
....
//省略很多代碼
}
從源碼上來看Snapshot主要有:
- mHeaps,Heap的列表,有四種可能default、app、image和zygote。GcRoot總是保存在default heap,並且GcRoot僅僅是指向保存在app、zygote的對象。
- mReferenceClasses ,保存各種Reference以及它的子類的Class信息,之前使用的KeyedWeakReference也保存在這裏
接着看看Heap的數據結構
public class Heap {
private final int mId;
private final String mName;
// 每個item對應着內存中棧的一幀
TLongObjectHashMap<StackFrame> mFrames = new TLongObjectHashMap<StackFrame>();
// StackTrace對應着內存中的棧,每個StackTrace裏面包含多個StackFrame
TIntObjectHashMap<StackTrace> mTraces = new TIntObjectHashMap<StackTrace>();
// 根對象,如內部字符串、jni局部變量等
ArrayList<RootObj> mRoots = new ArrayList<RootObj>();
// 線程列表
TIntObjectHashMap<ThreadObj> mThreads = new TIntObjectHashMap<ThreadObj>();
// Class信息
TLongObjectHashMap<ClassObj> mClassesById = new TLongObjectHashMap<ClassObj>();
Multimap<String, ClassObj> mClassesByName = ArrayListMultimap.create();
// 上面 Class對應的實例列表
private final TLongObjectHashMap<Instance> mInstances = new TLongObjectHashMap<Instance>();
// 關聯的snapshot對象
Snapshot mSnapshot;
...
}
Heap保存了
- StackFrame,對應着內存中棧的一幀。
- StackTrace,對應着內存中的棧。
- RootObj, 根對象
- ThreadObj ,線程
- ClassObj ,Class信息
- Instance,ClassObj的實例
14、HeapAnalyzer.findLeakTrace()
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
Instance leakingRef) {
//創建最短路徑Finder
ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
//查找找GcRoot到泄露對象的最短路徑
ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);//見15
...
//封裝分析的結果,省略了
}
這一步主要是創建最短路徑Finder,然後執行findPath方法去尋找
15、ShortestPathFinder.findPath()
Result findPath(Snapshot snapshot, Instance leakingRef) {
clearState();
canIgnoreStrings = !isString(leakingRef);
把GcRoot加入到隊列中
enqueueGcRoots(snapshot);
boolean excludingKnownLeaks = false;
LeakNode leakingNode = null;
//遍歷隊列,GcRoot就是加入了toVisitQueue或toVisitIfNoPathQueue其中一個
while (!toVisitQueue.isEmpty() || !toVisitIfNoPathQueue.isEmpty()) {
LeakNode node;
if (!toVisitQueue.isEmpty()) {
node = toVisitQueue.poll();
} else {
node = toVisitIfNoPathQueue.poll();
if (node.exclusion == null) {
throw new IllegalStateException("Expected node to have an exclusion " + node);
}
excludingKnownLeaks = true;
}
// 找到了泄露對象,結束搜索
if (node.instance == leakingRef) {
leakingNode = node;
break;
}
if (checkSeen(node)) {
continue;
}
//根據node類型分case
if (node.instance instanceof RootObj) {
visitRootObj(node); //見16 -------------【吸引注意力】------------!!!!!------------------------------
} else if (node.instance instanceof ClassObj) {
visitClassObj(node);
} else if (node.instance instanceof ClassInstance) {
visitClassInstance(node);
} else if (node.instance instanceof ArrayInstance) {
visitArrayInstance(node);
} else {
throw new IllegalStateException("Unexpected type for " + node.instance);
}
}
return new Result(leakingNode, excludingKnownLeaks);
}
這一步首先把GcRoot加入隊列,然後遍歷隊列。僅僅這個方法看不出什麼,接着看看被調用的visitRootObj方法到底做了什麼
16、ShortestPathFinder.visitRootObj()
private void visitRootObj(LeakNode node) {
RootObj rootObj = (RootObj) node.instance;
獲取Instance
Instance child = rootObj.getReferredInstance();
if (rootObj.getRootType() == RootType.JAVA_LOCAL) {
Instance holder = HahaSpy.allocatingThread(rootObj);
// We switch the parent node with the thread instance that holds
// the local reference.
Exclusion exclusion = null;
if (node.exclusion != null) {
exclusion = node.exclusion;
}
LeakNode parent = new LeakNode(null, holder, null, null, null);
LeakTraceElement.Type referenceType)
//入隊,enqueue方法內部構建parent和child關係,就是通過LeakNode對象的parent屬性關聯
//LeakNode childNode = new LeakNode(exclusion, child, parent, referenceName, referenceType);
enqueue(exclusion, parent, child, "<Java Local>", LOCAL);
} else {
enqueue(null, node, child, null, null);
}
}
大概看了下源碼,結合第15小節的代碼,就是圖的最短路徑問題了。在遍歷過程中發現泄露對象即可結束,返回結果就是GcRoot到泄露對象的最短路徑了。