上篇博客咱們寫到了 Java/Android 內存的分配以及相關 GC 的詳細分析,這篇博客咱們會繼續分析 Android 中內存泄漏的檢測以及相關案例,和 Android 的內存優化相關內容。
上篇:Android 性能優化以內存泄漏檢測以及內存優化(上)。
中篇:Android 性能優化以內存泄漏檢測以及內存優化(中)。
下篇:Android 性能優化以內存泄漏檢測以及內存優化(下)。
轉載請註明出處:blog.csdn.net/self_study/…
對技術感興趣的同鞋加羣544645972一塊兒交流。javascript
經過上篇博客咱們瞭解了 Android JVM/ART 內存的相關知識和泄漏的緣由,再來歸類一下內存泄漏的源頭,這裏咱們簡單將其歸爲一下三類:html
內存泄漏不像閃退的 BUG,排查起來相對要困難一些,比較極端的狀況是當你的應用 OOM 才發現存在內存泄漏問題,到了這種狀況纔去排查處理問題的話,對用戶的影響就太大了,爲此咱們應該在編碼階段儘早地發現問題,而不是拖到上線以後去影響用戶體驗,下面總結一下經常使用內存泄漏的定位和檢測工具:java
Lint 是 Android studio 自帶的靜態代碼分析工具,使用起來也很方便,選中須要掃描的 module,而後點擊頂部菜單欄 Analyze -> Inspect Code ,選擇須要掃描的地方便可:
android
StrictMode 是 Android 系統提供的 API,在開發環境下引入能夠更早的暴露發現問題給開發者,於開發階段解決它,StrictMode 最常被使用來檢測在主線程中進行讀寫磁盤或者網絡操做等耗時任務,把這些耗時任務放置於主線程會形成主線程阻塞卡頓甚至可能出現 ANR ,官方例子:git
public void onCreate() {
if (DEVELOPER_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate();
}複製代碼
把上面這段代碼放在早期初始化的 Application、Activity 或者其餘應用組件的 onCreate 函數裏面來啓用 StrictMode 功能,通常 StrictMode 只是在測試環境下啓用,到了線上環境就不要開啓這個功能。啓用 StrictMode 以後,在 logcat 過濾日誌的地方加上 StrictMode 的過濾 tag,若是發現一堆紅色告警的 log,說明可能就出現了內存泄漏或者其餘的相關問題了:
github
LeakCanary 是一個 Android 內存泄漏檢測的神器,正確使用能夠大大減小內存泄漏和 OOM 問題,地址:web
https://github.com/square/leakcanary複製代碼
集成 LeakCanary 也很簡單,在 build.gradle 文件中加入:正則表達式
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}複製代碼
而後在 Application 類中添加下面代碼:shell
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code...
}
}複製代碼
上面兩步作完以後就算是集成了 LeakCanary 了,很是簡單方便,若是程序出現了內存泄漏會彈出 notification,點擊這個 notification 就會進入到下面這個界面,或者集成 LeakCanary 以後在桌面會有一個 LeakCanary 的圖標,點擊進去是全部的內存泄漏列表,點擊其中一項一樣是進入到下面界面:
數據庫
Memory Monitor 是 Android Studio 自帶的一個監控內存使用狀態的工具,入口以下所示:
Column | Description |
---|---|
Class Name | 佔有這塊內存的類名 |
Total Count | 未被處理的數量 |
Heap Count | 在上面選擇的指定 heap 中的數量 |
Sizeof | 這個對象的大小,若是在變化中,就顯示 0 |
Shallow Size | 在當前這個 heap 中的全部該對象的總數 |
Retained Size | 這個類的全部對象佔有的總內存大小 |
Instance | 這個類的指定對象 |
Reference Tree | 指向這個選中對象的引用,還有指向這個引用的引用 |
Depth | 從 GC Root 到該對象的引用鏈路的最短步數 |
Shallow Size | 這個引用的大小 |
Dominating Size | 這個引用佔有的內存大小 |
而後能夠點擊展開右側的 Analyzer Tasks 項,勾選上須要檢測的任務,而後系統就會給你分析出結果:
MAT(Memory Analyzer Tools)是一個 Eclipse 插件,它是一個快速、功能豐富的 JAVA heap 分析工具,它能夠幫助咱們查找內存泄漏和減小內存消耗,MAT 插件的下載地址:Eclipse Memory Analyzer Open Source Project,上面經過 Android studio 生成的 .hprof 文件由於格式稍有不一樣,因此須要通過一個簡單的轉換,而後就能夠經過 MAT 去打開了:
能夠經過命令 adb shell dumpsys meminfo [package name]
來將指定 package name 的內存信息打印出來,這種模式能夠很是直觀地看到 Activity 未釋放致使的內存泄漏:
Android studio 還自帶一個 Allocation Tracker 工具,功能和 DDMS 中的基本差很少,這個工具能夠監控一段時間以內的內存分配:
咱們來看看常見的致使內存泄漏的案例:
因爲靜態變量的生命週期和應用同樣長,因此若是靜態變量持有 Activity 或者 Activity 中 View 對象的應用,就會致使該靜態變量一直直接或者間接持有 Activity 的引用,致使該 Activity 沒法釋放,從而引起內存泄漏,不過須要注意的是在大多數這種狀況下因爲靜態變量只是持有了一個 Activity 的引用,因此致使的結果只是一個 Activity 對象未能在退出以後釋放,這種問題通常不會致使 OOM 問題,只能經過上面介紹過的幾種工具在開發中去觀察發現。
這種問題的解決思路很簡單,就是不讓靜態變量直接或者間接持有 Activity 的強引用,能夠將其修改成 soft reference 或者 weak reference 等等之類的,或者若是能夠的話將 Activity Context 更換爲 Application Context,這樣就能保證生命週期一致不會致使內存泄漏的問題了。
咱們上面的 demo 中模擬的就是內部類對象持有外部類對象的引用致使外部類對象沒法釋放的問題,在 Java 中非靜態內部類和匿名內部類會持有他們所屬外部類對象的引用,若是這個非靜態內部類對象或者匿名內部類對象被一個耗時的線程(或者其餘 GC Root)直接或者間接的引用,甚至這些內部類對象自己就在作一些耗時操做,這樣就會致使這個內部類對象直接或者間接沒法釋放,內部類對象沒法釋放,外部類的對象也就沒法釋放形成內存泄漏,並且若是沒法釋放的對象積累起來就會形成 OOM,示例代碼以下所示:
public class SecondActivity extends AppCompatActivity{
private Handler handler;
private Bitmap bitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.pic);//decode 一個大圖來模擬內存沒法釋放致使的崩潰
findViewById(R.id.btn_second).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0);
}
}).start();
}
}複製代碼
這個問題的解決方法能夠根據實際狀況進行選擇:
這個很好理解,在一個錯誤的地方使用 Activity Context,形成 Activity Context 被靜態變量長時間引用致使沒法釋放而引起的內存泄漏,這個問題的處理方式也很簡單,若是能夠的話修改成 Application Context 或者將強引用變成其餘引用。
資源性對象好比(Cursor,File 文件等)每每都用了一些緩衝,咱們在不使用的時候應該及時關閉它們,以便它們的緩衝對象被及時回收,這些緩衝不只存在於 java 虛擬機內,還存在於 java 虛擬機外,若是咱們僅僅是把它的引用設置爲 null 而不關閉它們,每每會形成內存泄漏。可是有些資源性對象,好比 SQLiteCursor(在析構函數 finalize(),若是咱們沒有關閉它,它本身會調 close() 關閉),若是咱們沒有關閉它系統在回收它時也會關閉它,可是這樣的效率過低了。所以對於資源性對象在不使用的時候,應該調用它的 close() 函數,將其關閉掉,而後再置爲 null,在咱們的程序退出時必定要確保咱們的資源性對象已經關閉。
程序中常常會進行查詢數據庫的操做,可是常常會有使用完畢 Cursor 後沒有關閉的狀況,若是咱們的查詢結果集比較小,對內存的消耗不容易被發現,只有在常時間大量操做的狀況下才會出現內存問題,這樣就會給之後的測試和問題排查帶來困難和風險,示例代碼:
Cursor cursor = getContentResolver().query(uri...);
if (cursor.moveToNext()) {
... ...
}複製代碼
更正代碼:
Cursor cursor = null;
try {
cursor = getContentResolver().query(uri...);
if (cursor != null && cursor.moveToNext()) {
... ...
}
} finally {
if (cursor != null) {
try {
cursor.close();
} catch (Exception e) {
//ignore this
}
}
}複製代碼
在實際開發過程當中不免會有把對象添加到集合容器(好比 ArrayList)中的需求,若是在一個對象使用結束以後未將該對象從該容器中移除掉,就會形成該對象不能被正確回收,從而形成內存泄漏,解決辦法固然就是在使用完以後將該對象從容器中移除。
具體的能夠看看個人這篇博客:android WebView詳解,常見漏洞詳解和安全源碼(下)。
一些 Android 程序可能引用咱們的 Android 程序的對象(好比註冊機制),即便咱們的 Android 程序已經結束了,可是別的應用程序仍然還持有對咱們 Android 程序某個對象的引用,這樣也會形成內存不能被回收,好比調用 registerReceiver 後未調用unregisterReceiver。假設咱們但願在鎖屏界面(LockScreen)中,監聽系統中的電話服務以獲取一些信息,則能夠在 LockScreen 中定義一個 PhoneStateListener 的對象,同時將它註冊到 TelephonyManager 服務中,對於 LockScreen 對象,當須要顯示鎖屏界面的時候就會建立一個 LockScreen 對象,而當鎖屏界面消失的時候 LockScreen 對象就會被釋放掉,可是若是在釋放 LockScreen 對象的時候忘記取消咱們以前註冊的 PhoneStateListener 對象,則會間接致使 LockScreen 沒法被回收,若是不斷的使鎖屏界面顯示和消失,則最終會因爲大量的 LockScreen 對象沒有辦法被回收而引發 OOM,雖然有些系統程序自己好像是能夠自動取消註冊的(固然不及時),可是咱們仍是應該在程序結束時明確的取消註冊。
還有一種狀況是由於頻繁的內存分配和釋放,致使內存區域裏面存在不少碎片,當這些碎片足夠多,new 一個大對象的時候,全部的碎片中沒有一個碎片足夠大以分配給這個對象,可是全部的碎片空間加起來又是足夠的時候,就會出現 OOM,並且這種 OOM 從某種意義上講,是徹底可以避免的。
因爲產生內存碎片的場景不少,從 Memory Monitor 來看,下面場景的內存抖動是很容易產生內存碎片的:
內存優化請看下篇:Android 性能優化以內存泄漏檢測以及內存優化(下)。
blog.csdn.net/luoshengyan…
blog.csdn.net/luoshengyan…
blog.csdn.net/luoshengyan…
blog.csdn.net/luoshengyan…
blog.csdn.net/luoshengyan…
mp.weixin.qq.com/s?__biz=MzA…
geek.csdn.net/news/detail…
www.jianshu.com/p/216b03c22…
zhuanlan.zhihu.com/p/25213586
joyrun.github.io/2016/08/08/…
www.cnblogs.com/larack/p/60…
source.android.com/devices/tec…
blog.csdn.net/high2011/ar…
gityuan.com/2015/10/03/…
www.ayqy.net/blog/androi…
developer.android.com/studio/prof…
zhuanlan.zhihu.com/p/26043999