leakcanary探索
- leakcanary探索
- 什麼是leakcanary
- 粗略工作流程
- 記憶體洩露判定實作
- 系統回收
- 對象引用可達性判斷
- heapDump的分析
- 記憶體洩露判定實作
Android應用開發中,OutOfMemory是一個很讓人頭疼的問題,特别是疊代時間較長的項目中,問題定位較難。在gitHub上發現一個開源項目——leakcanary,可以輔助檢測并且定位記憶體洩漏。抽個時間研究一下它的實作。
什麼是leakcanary
A memory leak detection library for Android and Java.
leakcanary 包裝了簡單易用的接口,隻需簡單若幹行代碼,就可以開始工作。通過監控對象的回收情況,定位記憶體洩漏并自動執行HeapDump,自動分析hrpof檔案找出洩露的引用鍊,在通知欄自動提示使用者。
項目内置預設實作了ActivityRefWatcher,用于監控Activity,但隻能用于Android 4.0及其之上。如果想支援4.0之下的系統,需要定義BaseActivity複寫onDestroy來獲得統一監控觸發點。
理論上,leakcanary可以做到對象級别的記憶體洩露分析,同樣的需要自己實作對應的RefWatcher,在合适的時機進行觸發分析。
粗略工作流程
根據上面的流程圖,從技術實作上,個人比較關注的是兩個點:
- 如何判斷一個對象屬于記憶體洩漏
- 如何在hprof檔案裡面定位到問題并抓取出來
記憶體洩露判定實作
記憶體洩漏,指的是脫離了使用場景的對象,系統進行回收的時候無法成功釋放資源,導緻系列的問題。根據定義,思路大概是這樣:
系統回收
GcTrigger DEFAULT = new GcTrigger() {
@Override public void runGc() {
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perfom a gc.
Runtime.getRuntime().gc(); //不確定一定能夠觸發gc
enqueueReferences(); //sleep一定時間(100ms),來確定gc完成
System.runFinalization();//為什麼要放到enqueueReferences()後面?
}
如我們所知,Java裡面的gc擁有各種各樣的垃圾收集算法,gc的執行具有很大的不确定性。不管是用
System.gc()
還是
Runtime.getRuntime().gc()
,都是對jvm的一個建議,引發jvm的内部垃圾算法的權重,無法保證gc一定馬上執行。是以即使sleep了一定的時間等待gc,仍有極大的可能誤報。
對象引用可達性判斷
先讓我們來看一下邏輯的執行順序。
- 對象引用生成key并記錄
注意到這裡使用了public void watch(Object watchedReference, String referenceName) { ...檢查... final long watchStartNanoTime = System.nanoTime(); String key = UUID.randomUUID().toString(); retainedKeys.add(key);//直接記錄key //使用ReferenceQueue和WeakReference來記錄引用被回收 final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue); ...異步執行gc... }
ReferenceQueue
,查閱文檔得知,弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛拟機就會把這個弱引用加入到與之關聯的引用隊列中。
不同類型Reference的解釋
- 實際的gc和判斷過程
回顧這個過程,有一個比較明顯的問題,就是watch的時候是直接執行了void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime(); long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); removeWeaklyReachableReferences(); if (gone(reference) || debuggerControl.isDebuggerAttached()) { return; } gcTrigger.runGc();//實際執行gc removeWeaklyReachableReferences(); //如果引用對應的key仍然存在,認為是洩漏,執行heapDump if (!gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); File heapDumpFile = heapDumper.dumpHeap(); if (heapDumpFile == null) { // Could not dump the heap, abort. return; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key, reference.name, watchDurationMs, gcDurationMs,heapDumpDurationMs)); } } //簡單的key判斷 private boolean gone(KeyedWeakReference reference) { return !retainedKeys.contains(reference.key); } //凡是存在ReferenceQueue中的,都認為是被回收的,remove之 private void removeWeaklyReachableReferences() { // WeakReferences are enqueued as soon as the object to which they point to becomes weakly // reachable. This is before finalization or garbage collection has actually happened. KeyedWeakReference ref; while ((ref = (KeyedWeakReference) queue.poll()) != null) { retainedKeys.remove(ref.key); } }
,意圖在後期通過GC來利用ReferenceQueue的enqueue,進而retainedKeys.add(key)
。而在前面的分析中,GC的不确定是很強的,是以存在誤報的可能。remove(key)
heapDump的分析
待續