在介紹記憶體流失之前很有必要提及一下Android系統的記憶體回收機制。Java GC(Garbage Collection,垃圾收集,記憶體回收)機制,是Java與C++/C的主要區別之一,作為Java開發人員,不需要專門編寫記憶體回收和垃圾清理代碼,對記憶體泄露和溢出的問題,也不需要像C程式員那樣戰戰兢兢。這是因為在Java虛擬機器中,存在自動記憶體管理和垃圾清掃機制。概括地說,該機制對虛擬機器中的記憶體進行標記,並確定哪些記憶體需要回收,根據一定的回收策略,自動的回收記憶體,永不停息(Nerver Stop)的保證虛擬機器中的記憶體空間,防止出現記憶體泄露和溢出問題。Android系統的記憶體回收是基於可達性分析演算法(根搜尋演算法)的。從GC Roots(每種具體實現對GC Roots有不同的定義)作為起點,向下搜尋它們引用的對象,可以產生一棵引用樹,樹的節點視為可達對象,反之視為不可達,不可達對象會被回收。
舉個例子,我們在開發中經常使用單例模式,單例的靜態特性導致其生命週期同應用一樣長。有時建立單例時如果我們需要Context對象,如果傳入的是Application的Context那麼不會有問題。如果傳入的是Activity的Context對象,那麼當Activity生命週期結束時,該Activity的引用依然被單例持有,所以不會被回收,而單例的生命週期又是跟應用一樣長,這個情況就叫做記憶體泄露(Memory Leak)。它指的是當你不再需要某個執行個體後,但是這個對象卻仍然被引用,防止被記憶體回收(Prevent from being bargage collected)。
public class Util {
private Context mContext;
private static Util sInstance;
private Util(Context context) {
this.mContext = context;
}
public static Util getInstance(Context context) {
if (sInstance == null) {
sInstance = new Util(context);
}
return sInstance;
}
}
本傑明 富蘭克林曾說:A small leak will sink a great ship(小漏不補沉大船)。基於Android系統的裝置一般來說記憶體就不大,特別是早期的Android裝置,記憶體流失是很致命的,記憶體流失積攢到一定程度會引發記憶體溢出(OOM),如果處理不當直接導致程式崩潰退出。
常見的記憶體流失
一般來說在開發中我們經常會犯下下面幾個錯誤,導致記憶體流失。這幾個都是前人踩坑總結出來的,非常有參考價值,至少我在排查解決記憶體流失的時候是這樣的。
一. 單例造成的記憶體流失
Android的單例模式非常受開發人員的喜愛,不過使用的不恰當的話也會造成記憶體流失。因為單例的靜態特性使得單例的生命週期和應用的生命週期一樣長,這就說明了如果一個對象已經不需要使用了,而單例對象還持有該對象的引用,那麼這個對象將不能被正常回收,這就導致了記憶體流失。例子見上面那段代碼。
二、非靜態內部類建立靜態執行個體造成的記憶體流失
有的時候我們可能會在啟動頻繁的Activity中,為了避免重複建立相同的資料資源,在Activity內部建立了一個非靜態內部類的單例,每次啟動Activity時都會使用該單例的資料,這樣雖然避免了資源的重複建立,不過這種寫法卻會造成記憶體流失,因為非靜態內部類預設會持有外部類的引用,而又使用了該非靜態內部類建立了一個靜態執行個體,該執行個體的生命週期和應用的一樣長,這就導致了該靜態執行個體一直會持有該Activity的引用,導致Activity的記憶體資源不能正常回收。例子如下
public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (mResource == null) {
mResource = new TestResource();
}
//......
}
class TestResource {
//......
}
}
三、Handler造成的記憶體流失
Handler的使用造成的記憶體流失問題應該說最為常見了,平時在處理網路任務或者封裝一些請求回調等api都應該會藉助Handler來處理,我們經常在Activity裡面這樣定義一個私人的Handler對象並初始化,這種建立Handler的方式會造成記憶體流失,由於mHandler是Handler的非靜態匿名內部類的執行個體,所以它持有外部類Activity的引用,我們知道訊息佇列是在一個Looper線程中不斷輪詢處理訊息,那麼當這個Activity退出時訊息佇列中還有未處理的訊息或者正在處理訊息,而訊息佇列中的Message持有mHandler執行個體的引用,mHandler又持有Activity的引用,所以導致該Activity的記憶體資源無法及時回收,引發記憶體流失。
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//.....
}
};
四、資源未關閉造成的記憶體流失
對於使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源的使用,應該在Activity銷毀時及時關閉或者登出,否則這些資源將不會被回收,造成記憶體流失。
檢測記憶體流失的常見工具
LeakCanary是Square開源了一個記憶體泄露自動探測神器 。對應的github倉庫地址:https://github.com/square/leakcanary 。使用非常簡單,在build.gradle中引入包依賴:
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中的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);
整合後什麼都不用做,按照正常測試,當有記憶體流失發生後,應用會通過系統通知欄發出通知,點擊通知就可以進入查看記憶體流失的具體資訊。其實無論是MAT工具的記憶體分析,還是AndroidStudio中內建的分析工具亦或是LeakCanary,原理都是一樣的,都是dump java heap出來進行分析,找到泄漏的問題,只是LeakCanary幫我們把分析的工作做了。