什么是內存泄漏
對於不同的語言平台來說,進行標記回收內存的算法是不一樣的,像 Android(Java)則采用 GC-Root 的標記回收算法。下面這張圖就展示了 Android 內存的回收管理策略(圖來自Google 2011的IO大會)
圖中的每個圓節點代表對象的內存資源,箭頭代表可達路徑。當圓節點與 GC Roots 存在可達路徑時,表示當前資源正被引用,虛擬機是無法對其進行回收的(如圖中的黃色節點)。反過來,如果圓節點與 GC Roots 不存在可達路徑,則意味着這塊對象的內存資源不再被程序引用,系統虛擬機可以在 GC 過程中將其回收掉。
有了上面的內存回收的栗子,那么接下來就可以說說什么是內存泄漏了。從定義上講,Android(Java)平台的內存泄漏是指 沒有用的對象資源任與GC-Root保持可達路徑,導致系統無法進行回收。 舉一個最簡單的栗子,我們在 Activity 的 onCreate 函數中注冊一個廣播接收者,但是在 onDestory 函數中並沒有執行反注冊,當 Activity 被 finish 掉時,Activity 對象已經走完了自身的生命周期,應該被資源回收釋放掉,但由於沒有反注冊, 此時 Activity 和 GC-Root 間任然有可達路徑存在,導致 Activity 雖然被銷毀,但是所占用的內存資源卻無法被回收掉。類似的栗子其實有很多,不一一例舉了。對於 Android(Java)內存回收管理想要再深入了解的童鞋,可以看看下面資源:
可以作為GC Roots的對象包括下面幾種:
-
虛擬機棧中引用的對象, 一般是當前在使用中局部變量
-
方法區中類靜態屬性引用的對象, 就是靜態變量對應的對象
-
方法區中常量引用的對象
-
本地方法棧中JNI(即一般說的Native方法)引用的對象
MAT分析內存泄漏的時候,也是查看對象到GC Roots的引用鏈,來定位泄漏代碼的位置。
所以未使用的對象直接或間接地被GC Roots引用時會讓GC無法回收,從而產生內存泄漏。
http://www.cnblogs.com/daqiang5566/p/6150235.html
靜態代碼分析工具 —— Lint
Lint 是 Android Studio 自帶的工具,使用姿勢很簡單 Analyze -> Inspect Code 然后選擇想要掃面的區域即可
對可能引起泄漏的編碼,Lint 都會進行溫馨提示。
這里只是拋磚引玉的介紹 Lint ,實際上玩法還有很多,大家可以自行拓展學習。除了 Lint 外,還有像 FindBugs 、 Checkstyle 等靜態代碼分析工具也是很不錯的。
嚴苛模式 —— StrictMode
(1)是關於常用的監控方面的,
Disk Writes 磁盤寫
Network access 網絡訪問
Custom Slow Code 自定義的運行速度慢的代碼分析
(2)另外一類是關於VM虛擬機等方面的策略
內存泄露的SQLite對象
內存泄露的未釋放的對象
https://developer.android.com/reference/android/os/StrictMode.html
以官網的示例代碼為栗子,一般 StrictMode 只在測試環境下啟用,到了生產環境就會進行關閉,通常我們都會借助 BuildConfig.DEBUG 來實現。
啟用 StrictMode 后,在過濾日志的地方加上 StrictMode 的過濾 Tag ,如果手機連接着電腦進行開發,定期觀察一下 StrictMode 這個 Tag 下的日志,一般你看到一大堆紅色告警的 Log.就需要好好排查一下是否跟內存泄漏有關了。
詳細的StrictMode 請參考:http://www.cnblogs.com/daqiang5566/p/6163756.html
Android Memory Monitor
AndroidStudio 提供的工具,用於監控應用的內存使用狀態,在開發中也是非常實用的工具,可以用來打印出內存的狀態信息。
打印獲得的內存信息如下,可以通過右上角的綠色三角形按鈕去分析泄漏的 Activity 和 一些重復的字符串,目前只支持這兩個,希望 Google 后面能夠加入更多可選分析規則
同樣,這里也只是拋磚引玉的簡單介紹,關於它的使用在官方文檔已經說得很詳細了,需要的童鞋自行查看下方鏈接(需科學上網):
Memory Analyzer (MAT)
老牌子分析工具,可以從 http://www.eclipse.org/mat/ 下載獲得,網上關於 MAT 使用的文章好多,大家可以自行查找。上面的 Android Memory Monitor 生成的對儲存信息文件可以配置 MAT 一起來分析使用,由於 Android Memory Monitor 生成的 hprof 文件不是標准格式,所以需要做一下轉換,然后導入 MAT
然后通過 OQL 先定位出泄漏的對象
通過排除除了強引用之外的其他引用鏈,最后分析到 GC Root 的位置
MAT 使用起來相對繁瑣,但不失為定位根源問題的利器。
adb shell 命令
使用 adb shell dumpsys meminfo [PackageName],可以打印出指定包名的應用內存信息
使用該命令可以很直觀的觀察到 Activity 的泄漏問題,是我平常分析比較常用的一種方式。除了使用命令外,AndroidStudio 也提供了下面的功能,和使用命令是一樣效果的。
如果對 adb shell 命令感興趣,更多的信息可以看下面提供的資源:
以上就是我在做內存泄漏分析的時候會用到的工具,通常都是結合起來用,畢竟每個工具都有優缺點,通過使用多個工具互補分析問題可以極大的提高我們的效率和最終取得的效果。
內存泄露工具:leakcanary 也可以檢車內存泄露問題,可自行百度
泄漏的解決策略
(1) 注意Activity的泄漏
- 內部類引用導致Activity泄漏
- Activity Context被間接引用
對於大部分非必須使用Activity Context的情況(Dialog的Context就必須是Activity Context),我們都可以考慮使用Application Context而不是Activity的Context,這樣可以避免不經意的Activity泄露
(2) 注意靜態變量和單例模式
靜態變量是作為GC Roots,在Android其生命周期基本和進程一樣長,所以要非常靜態變量引用其他生命周期的對象。雖然單例模式簡單實用,提供了很多便利性,但是因為單例的生命周期和應用保持一致,使用不合理很容易出現持有對象的泄漏。
(3) 注意容器中對象泄漏
有時候,我們為了提高對象的復用性把某些對象放到緩存容器中,可是如果這些對象沒有及時從容器中清除,也是有可能導致內存泄漏的。例如,針對2.3的系統,如果把drawable添加到緩存容器,因為drawable與View的強應用,很容易導致activity發生泄漏。而從4.0開始,就不存在這個問題。解決這個問題,需要對2.3系統上的緩存drawable做特殊封裝,處理引用解綁的問題,避免泄漏的情況。
(4) 注意監聽器的注銷
(5) …
(6) 及時關閉Cursor
在程序中我們經常會進行查詢數據庫的操作,但時常會存在不小心使用Cursor之后沒有及時關閉的情況。這些Cursor的泄露,反復多次出現的話會對內存管理產生很大的負面影響,我們需要謹記對Cursor對象的及時關閉。
深入理解Java虛擬機:JVM高級特性與最佳實踐(第2版)
Google IO 2011 Memory management for Android Apps
泄漏的源頭
了解完內存泄漏的理論知識后,再來歸類一下內存泄漏的源頭。這里我將其歸位以下三類:
自身編碼引起
由項目開發人員自身的編碼造成。
第三方代碼引起
這里的第三方代碼包含兩類:第三方非開源的SDK和開源的第三方框架。
系統原因
由 Android 系統自身造成的泄漏,如像 WebView 、 InputMethodManager 等引起的問題,還有某些第三方 ROM 存在的問題。
泄漏的定位
內存泄漏不像閃退的BUG,排查起來相對要比較困難些,比較極端的情況是當你的應用 OOM 了才發現存在內存泄漏問題,到了這種情況才去排查處理問題的話,對用戶的影響就太大了。為此,我們能夠在編碼中盡早發現到問題就不要拖到上線之后才去填坑,下面介紹一些我比較常用排查內存泄漏的工具。
參考地址:http://magic.360.cn/index.html
2. 代碼規范檢查
3. 內存泄露檢查
4. 日志輸出檢查
5. 空指針檢查
6. 多線程檢查