簡述
在性能優化中,內存是一個不得不聊的話題;然而內存泄漏,顯示已經成爲內存優化的一個重量級的方向。當前流行的內存泄漏分析工具中,不得不提的就是LeakCanary框架;這是一個集成方便, 使用便捷,配置超級簡單的框架,實現的功能倒是極爲強大的。android
不騙你,真的,使用就是這麼簡單 ?!
1. 你須要添加到配置的只有這個算法
dependencies {性能優化
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'app
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'框架
}eclipse
2. 你確定須要初始化一下,固然, 推薦在Application中異步
public class MyApplicationextends Application {ide
public void onCreate() {源碼分析
super.onCreate();
LeakCanary.install(this);
}
}
3. 什麼?你還在下一步?已經結束了!經過以上配置,你就能夠輕鬆使用LeakCanary檢測內存泄漏了
關於LeakCanary的詳細使用教程,建議去看:LeakCanary中文使用說明
閒聊結束,我想大家來確定不是看我這些廢話的,那麼如今進入正題!本篇咱們所想的,就是LeakCanary爲何能夠這麼神奇,它是怎麼檢測內存泄漏的?下面咱們解開謎題!
《LeakCanary原理》 核心類分析
01 LeakCanary 源碼解析
02 LeakCanary SDK提供類
03 DisplayLeakActivity 內存泄漏的查看頁面
04 HeapAnalyzerService 內存堆分析服務, 爲了保證App進程不會所以受影響變慢&內存溢出,運行於獨立的進程
05 HeapAnalyzer 分析由RefWatcher生成的堆轉儲信息, 驗證內存泄漏是否真實存在
06 HeapDump 堆轉儲信息類,存儲堆轉儲的相關信息
07 ServiceHeapDumpListener 一個監聽,包含了開啓分析的方法
08 RefWatcher 核心類, 翻譯自官方: 檢測不可達引用(可能地),當發現不可達引用時,它會觸發 HeapDumper(堆信息轉儲)
09 ActivityRefWatcher Activity引用檢測, 包含了Activity生命週期的監聽執行與中止
經過以上列表,讓你們對LeakCanary框架的主要類有個大致的瞭解,並基於以上列表,對這個框架的大致功能有一個模糊的猜想。
漫無目的的看源碼,很容易迷失在茫茫的Code Sea中,不管是看源碼,仍是接手別人的項目,都是如此;所以,帶着問題與目的性來看這些複雜的東西是頗有必要的,也使得咱們閱讀效率大大提升;想要了解LeakCanary,咱們最大的疑惑是什麼,我列出來,看看是與你不約而同。
Question1: 在Application中初始化以後,它是如何檢測全部的Activity頁面的 ?
Question2: 內存泄漏的斷定條件是什麼 ? 檢測內存泄漏的機制原理是什麼?
Question3: 檢測出內存泄漏後,它又是如何生成泄漏信息的? 內存泄漏的輸出軌跡是怎麼獲得的?
回顧一下這個框架,其實咱們想了解的機制不外乎三:
1. 內存泄漏的檢測機制
2. 內存泄漏的斷定機制
3. 內存泄漏的軌跡生成機制
咱們會在源碼分析最後,依次回答以上的三個問題,可能在閱讀源碼以前,咱們先要對內存泄漏作一些基礎概念與原理的理解。
什麼是內存泄漏(MemoryLeak)?
你們對這個概念應該不陌生吧,當咱們使用一個Bitmap,使用完成後,沒有recycle回收;當咱們使用Handler, 在Activity銷燬時沒有處理;當咱們使用Cursor,最後沒有close並置空;以上這些都會致使必定程度上的內存泄漏問題。那麼,什麼是內存泄漏?
內存泄漏(Memory Leak)是指程序中己動態分配的堆內存因爲某種緣由程序未釋放或沒法釋放,形成系統內存的浪費,致使程序運行速度減慢甚至系統崩潰等嚴重後果。
以上是百度百科的解釋,總結下爲:內存泄漏是不使用或用完的內存,由於某些緣由沒法回收,形成的一種內存浪費;內存泄漏的本質是內存浪費。以我的理解來解釋,通俗一點就是
1. GC回收的對象必須是當前沒有任何引用的對象
2.當對象在使用完成後(對咱們而言已是垃圾對象了), 咱們沒有釋放該對象的引用,致使GC不能回收該對象而繼續佔用內存
3.垃圾對象依舊佔用內存,這塊內存空間便浪費了
內存泄漏與內存溢出的區別是什麼?
從名稱來看,一個泄漏,一個溢出,其實很好理解。
內存泄漏: 垃圾對象依舊佔據內存,如水龍頭的泄漏,水原本是屬於水源的, 可是水龍頭沒關緊,那麼泄漏到了水池;再來看內存,內存原本應 該被回收,可是依舊在內存堆中;總結一下就是內存存在於不應存在的地方(沒用的地方)
內存溢出: 內存佔用達到最大值,當須要分配內存時,已經沒有內存能夠分配了,就是溢出;依舊以水池爲例, 水池的水若是滿了,那麼若是繼 續須要從水龍頭流水的話,水就會溢出。總結一下就是,內存的分配超出最大閥值,致使了一種異常
明白了二者的概念,那麼二者有什麼關係呢?
內存的溢出是內存分配達到了最大值,而內存泄漏是無用內存充斥了內存堆;所以內存泄漏是致使內存溢出的元兇之一,並且是很大的元兇;由於內存分配完後,哪怕佔用再大,也會回收,而泄漏的內存則否則;當清理掉無用內存後,內存溢出的閥值也會相應下降。
JVM如何斷定一個對象是垃圾對象?
該問題也即垃圾對象搜索算法,JVM採用圖論的可達遍歷算法來斷定一個對象是不是垃圾對象, 若是對象A是可達的,則認爲該對象是被引用的,GC不會回收;若是對象A或者塊B(多個對象引用組成的對象塊)是不可達的,那麼該對象或者塊則斷定是不可達的垃圾對象,GC會回收。
以上科普的兩個小知識:1) 內存泄漏 2) JVM搜索算法 是閱讀LeakCanary源碼的基本功,有助於源碼的理解與記憶。好了,下面來看一下LeakCanary的源碼,看看LeakCanary是怎麼工做的吧!
既然LeakCanary的初始化是從install()開始的,那麼從init開始看
回顧一下核心類模塊可知,內存分析模塊是在獨立進程中執行的,這麼設計是爲了保證內存分析過程不會對App進程形成消極的影響,如使App進程變慢或致使out of Memory問題等。所以
第一步: 判斷APP進程與內存分析進程是否屬於同一進程;若是是, 則返回空的RefWatcher DISABLED;若是不是,往下走,看第二步
第二步: enableDisplayLeakActivity 開啓顯示內存泄漏信息的頁面
第三步:初始化一個ServiceHeapDumpListener,這是一個開啓分析的接口實現類,類中定義了analyze方法,用於開啓一個DisplayLeakService服務,從名字就能夠看出,這是一個顯示內存泄漏的輔助服務
第四步:初始化兩個Watcher, RefWatcher和ActivityRefWatcher. 這兩個Watcher的做用分別爲分析內存泄漏與監聽Activity生命週期
經過以上代碼分析,咱們能夠得出第一個問題的答案。LeakCanary經過ApplicationContext統一註冊監聽的方式,來監察全部的Activity生命週期,並在Activity的onDestroy時,執行RefWatcher的watch方法,該方法的做用就是檢測本頁面內是否存在內存泄漏問題。
下面咱們繼續來分析核心類RefWatcher中的源碼,檢測機制的核心邏輯便在RefWatcher中;相信閱讀完這個類後,第二個問題的答案便呼之欲出了。
既然想弄明白RefWatcher作了什麼,那麼先來看一下官方的解釋
從上面圖能夠看出官方的解釋。 RefWatcher是一個引用檢測類,它會監聽可能會出現泄漏(不可達)的對象引用,若是發現該引用多是泄漏,那麼會將它的信息收集起來(HeapDumper).
從RefWatcher源碼來看,核心方法主要有兩個: watch() 和 ensureGone()。若是咱們想單獨監聽某塊代碼,如fragment或View等,咱們須要手動去調用watch()來檢測;由於上面講過,默認的watch()僅執行於Activity的Destroy時。watch()是咱們直接調用的方法,ensureGone()則是具體如何處理了,下面咱們來看一下
上圖爲watch()的源碼, 咱們先來看一下官方的註釋
監聽提供的引用,檢查該引用是否能夠被回收。這個方法是非阻塞的,由於檢測功能是在Executor中的異步線程執行的
從上述源碼能夠看出,watch裏面只是執行了必定的準備工做,如判空(checkNotNull), 爲每一個引用生成一個惟一的key, 初始化KeyedWeakReference;關鍵代碼仍是在watchExecutor中異步執行。引用檢測是在異步執行的,所以這個過程不會阻塞線程。
以上是檢測的核心代碼實現,從源碼能夠看出,檢測的流程:
1) 移除不可達引用,若是當前引用不存在了,則不繼續執行
2) 手動觸發GC操做,gcTrigger中封裝了gc操做的代碼
3) 再次移除不可達引用,若是引用不存在了,則不繼續執行
4) 若是兩次斷定都沒有被回收,則開始分析這個引用,最終生成HeapDump信息
總結一下原理:
1. 弱引用與ReferenceQueue聯合使用,若是弱引用關聯的對象被回收,則會把這個弱引用加入到ReferenceQueue中;經過這個原理,能夠看出removeWeaklyReachableReferences()執行後,會對應刪除KeyedWeakReference的數據。若是這個引用繼續存在,那麼就說明沒有被回收。
2. 爲了確保最大保險的斷定是否被回收,一共執行了兩次回收斷定,包括一次手動GC後的回收斷定。兩次都沒有被回收,很大程度上說明了這個對象的內存被泄漏了,但並不能100%保證;所以LeakCanary是存在極小程度的偏差的。
上面的代碼,總結下流程就是
斷定是否回收(KeyedWeakReference是否存在該引用), Y -> 退出, N -> 向下執行
手動觸發GC
斷定是否回收, Y -> 退出, N-> 向下執行
兩次未被回收,則分析引用狀況:
1) humpHeap : 這個方法是生成一個文件,來保存內存分析信息
2) analyze: 執行分析
經過以上的代碼分析,第二個問題的答案已經浮出水面了吧!
接下來分析內存泄漏軌跡的生成~
最終的調用,是在RefWatcher中的ensureGone()中的最後,如圖
很明顯,走的是heapdumpListener中的analyze方法,繼續追蹤heapdumpListener是在LeakCanary初始化的時候初始化並傳入RefWatcher的,如圖
打開進入ServiceHeapDumpListener,看裏面實現,如圖
調用了HeapAnalyzerService,在單獨的進程中進行分析,如圖
HeapAnalyzerService中經過HeapAnalyzer來進行具體的分析,查看HeapAnalyzer源碼,如圖
進行分析時,調用了openSnapshot方法,裏面用到了SnapshotFactory
從上圖能夠看出,這個版本的LeakCanary採用了MAT對內存信息進行分析,並生成結果。其中在分析時,分爲findLeakingReference與findLeakTrace來查找泄漏的引用與軌跡,根據GCRoot開始按樹形結構依次建議當前引用的軌跡信息。
經過上述分析,最終得出的結果爲:
1. Activity檢測機制是什麼?
答: 經過application.registerActivityLifecycleCallbacks來綁定Activity生命週期的監聽,從而監控全部Activity; 在Activity執行onDestroy時,開始檢測當前頁面是否存在內存泄漏,並分析結果。所以,若是想要在不一樣的地方都須要檢測是否存在內存泄漏,須要手動添加。
2. 內存泄漏檢測機制是什麼?
答: KeyedWeakReference與ReferenceQueue聯合使用,在弱引用關聯的對象被回收後,會將引用添加到ReferenceQueue;清空後,能夠根據是否繼續含有該引用來斷定是否被回收;斷定回收, 手動GC, 再次斷定回收,採用雙重斷定來確保當前引用是否被回收的狀態正確性;若是兩次都未回收,則肯定爲泄漏對象。
3. 內存泄漏軌跡的生成過程 ?
答: 該版本採用eclipse.Mat來分析泄漏詳細,從GCRoot開始逐步生成引用軌跡。
經過整篇文章分析,你還在疑惑麼?