1 記憶體泄漏簡介
記憶體泄漏是指記憶體空間使用完畢后無法被釋放的現象,盡管Java有垃圾回識訓制(GC),但是對于還保持著參考,邏輯上卻已經不會再用到的物件,垃圾回收器不會回收它們,
記憶體泄漏帶來的危害:
- 用戶對單次的記憶體泄漏并沒有什么感知,但當可用的空閑空間越來越少,GC就會更容易被觸發,GC進行時會停止其他執行緒的作業,因此有可能會造成界面卡頓等情況,
- 后續需要分配記憶體的時候,很容易導致記憶體空間不足而出現 OOM(記憶體溢位),
2 常見的記憶體泄漏場景
2.1 static 關鍵字修飾成員變數
被 static 關鍵字修飾的成員變數的生命周期等于應用程式的生命周期,若使被 static 關鍵字修飾的成員變數參考耗費資源過多的實體(如Context),則容易出現該成員變數的生命周期大于參考實體生命周期的情況,當參考實體需結束生命周期銷毀時,會因靜態變數的持有而無法被回收,從而出現記憶體泄露,
- static Activity

這里也會提示有記憶體泄漏, - static View
如果一個 View 初始化耗費大量資源,而且在一個 Activity 生命周期內保持不變,那可以把它變成 static,加載到視圖樹上(View Hierachy),當 Activity 被銷毀時,應當釋放資源,否則就會導致記憶體泄漏,
解決方案:
- 盡量避免 static 成員變數參考資源耗費過多的實體(如 Context),若需參考 Context,則盡量使用Applicaiton的 Context,
- 使用弱參考(WeakReference) 代替強參考持有實體,
2.2 非靜態內部類/ 匿名類
非靜態內部類 / 匿名類默認持有外部類的參考,而靜態內部類則不會,常見的情況有以下三種,
2.2.1 非靜態內部類
如果非靜態內部類所創建的實體是靜態的,其生命周期等于應用的生命周期,非靜態內部類默認持有外部類的參考而導致外部類無法釋放,最終造成記憶體泄露,即外部類中持有非靜態內部類的靜態物件,
public class MainActivity extends AppCompatActivity {
//非靜態內部類的靜態實體參考
public static InnerClass innerClass = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//保證非靜態內部類的實體只有1個
if (innerClass == null) {
innerClass = new InnerClass();
}
}
// 非靜態內部類
private class InnerClass {
//...
}
}
當 MainActivity 銷毀時,因非靜態內部類單例的參考,innerClass 的生命周期等于應用的生命周期,持有外部類 MainActivity 的參考,故 MainActivity 無法被 GC 回收,從而導致記憶體泄漏,
解決方案:
- 將非靜態內部類設定為:靜態內部類(靜態內部類默認不持有外部類的參考)
- 該內部類抽取出來封裝成一個單例
- 盡量避免非靜態內部類所創建的實體是靜態的,
2.2.2 多執行緒:AsyncTask、實作 Runnable 介面、繼承 Thread 類
當作業執行緒正在處理任務時,如果外部類銷毀, 由于作業執行緒實體持有外部類參考,將使得外部類無法被垃圾回收器(GC)回收,從而造成記憶體泄露,
2.2.2.1 AsyncTask
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startAsyncTask();
}
private void startAsyncTask() {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
//執行耗時操作
while(true);
}
}.execute();
}
}
2.2.2.2 實作 Runnable 介面
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
class MyRunnable implements Runnable {
@Override
public void run() {
//執行耗時操作
}
}
}
2.2.2.3 繼承 Thread 類
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyThread().start();
}
private class MyThread extends Thread {
@Override
public void run() {
//執行耗時操作
}
}
}
解決方案:
- 使用靜態內部類的方式,靜態內部類不默認持有外部類的參考,
private static class MyThread extends Thread {
@Override
public void run() {
//執行耗時操作
}
}
- 當外部類結束生命周期時,強制結束執行緒,使得作業執行緒實體的生命周期與外部類的生命周期同步,
@Override
protected void onDestroy() {
super.onDestroy();
myThread.interrupt();
}
2.3 Handler
在 Handler 訊息佇列還有未處理的訊息 / 正在處理訊息時,訊息佇列中的 Message 持有 Handler 實體的參考,如果 Handler 是非靜態內部類 / 匿名內部類(2種使用方式),就會默認持有外部類的參考(如 MainActivity 實體),

上述的參考關系會一直保持,直到 Handler 訊息佇列中的所有訊息被處理完畢,在 Handler 訊息佇列還有未處理的訊息 / 正在處理訊息時,此時若需銷毀外部類 MainActivity,但由于上述參考關系,垃圾回收器(GC)無法回收 MainActivity,從而造成記憶體泄漏,
public class MainActivity extends AppCompatActivity {
private MyHandler myHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myHandler = new MyHandler();
new Thread() {
@Override
public void run() {
try {
//執行耗時操作
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//發送訊息
myHandler.sendEmptyMessage(1);
}
}.start();
}
private class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
//處理訊息事件
}
}
}
解決方案:
- 使用靜態內部類+弱參考的方式,保證外部類能被回收,因為弱參考的物件擁有短暫的生命周期,在垃圾回收器執行緒掃描時,一旦發現了具有弱參考的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體,
public class MainActivity extends AppCompatActivity {
private MyHandler myHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myHandler = new MyHandler(this);
new Thread() {
@Override
public void run() {
try {
//執行耗時操作
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//發送訊息
myHandler.sendEmptyMessage(1);
}
}.start();
}
public void test() {
Log.d("MainActivity", "test");
}
private static class MyHandler extends Handler {
//定義弱參考實體
private WeakReference<Activity> reference;
//在構造方法中傳入需持有的Activity實體
public MyHandler(Activity activity) {
//使用 WeakReference 弱參考持有 Activity 實體
reference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
//處理訊息事件
//呼叫Activity實體中的方法
((MainActivity) reference.get()).test();
}
}
}
- 當外部類結束生命周期時,清空 Handler 內訊息佇列,
使用建議:
為了保證 Handler 中訊息佇列中的所有訊息都能被執行,此處推薦使用解決方案1,即靜態內部類+弱參考的方式,
2.4 資源物件使用后未關閉
對于資源的使用(如廣播 BraodcastReceiver、檔案流 File、資料庫游標 Cursor、圖片資源 Bitmap等),若在 Activity 銷毀時無及時關閉 / 注銷這些資源,則這些資源將不會被回收,從而造成記憶體泄漏,
解決方案:
//對于廣播BroadcastReceiver:注銷注冊
unregisterReceiver(broadcastReceiver);
//對于檔案流File:關閉流
inputStream / outputStream.close();
//對于資料庫游標cursor:使用后關閉游標
cursor.close();
//對于圖片資源Bitmap:Android分配給圖片的記憶體只有8M,若1個Bitmap物件占記憶體較多,當它不再被使用時,應呼叫recycle()回收此物件的像素所占用的記憶體;最后再賦為null
bitmap.recycle();
bitmap = null;
// 對于影片(屬性影片),將影片設定成無限回圈播放setRepeatCount(ValueAnimator.INFINITE);后
// 在Activity退出時記得停止影片
animator.cancel();
關閉以上物件的時候注意做非空判斷,
2.5 WebView記憶體泄露
WebView 內部的一些執行緒持有 Activity 物件,使得 Activity 無法釋放,從而導致記憶體泄漏,
解決方案:
@Override
protected void onDestroy() {
if (mWebView != null) {
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.stopLoading();
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.clearView();
mWebView.removeAllViews();
mWebView.destroy();
mWebView = null;
}
super.onDestroy();
}
不建議在 xml 中創建 WebView,因為在 xml 中創建的 WebView 會持有 Activity 的 Context 物件,
2.6 單例模式造成的記憶體泄漏
由于單例的靜態特性使得其生命周期跟應用的生命周期一樣長,所以如果使用不恰當的話,很容易造成記憶體泄漏,
public class Singleton {
private static Singleton instance;
private Context mContext;
private Singleton(Context context){
this.mContext = context;
}
public static Singleton getInstance(Context context){
if (instance == null){
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton(context);
}
}
}
return instance;
}
}
這是一個單例模式,當創建這個單例的時候,由于需要傳入一個 Context:
- 如果此時傳入的是 Application 的 Context,因為 Application 的生命周期就是整個應用的生命周期,所以這沒有問題,
- 如果此時傳入的是 Activity 的 Context,當這個 Context 所對應的 Activity 退出時,由于該 Context 的參考被單例物件所持有,其生命周期等于整個應用程式的生命周期,所以當前 Activity 的記憶體并不會被回收,這就造成泄漏了,
解決方案:
將 new Singleton(context) 改為 new Singleton(context.getApplicationContext()) 即可,這樣便和傳入的 Activity 沒關系了,
public class Singleton {
private static Singleton instance;
private Context mContext;
private Singleton(Context context){
this.mContext = context;
}
public static Singleton getInstance(Context context){
if (instance == null){
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton(context.getApplicationContext());// 使用Application 的context
}
}
}
return instance;
}
}
3 記憶體泄漏分析工具
3.1 lint
lint 是一個靜態代碼分析工具,同樣也可以用來檢測部分會出現記憶體泄露的代碼,平時編程注意 lint 提示的各種黃色警告即可,如:

也可以手動檢測,在 Android Studio 中選擇 Analyze->Inspect Code,

然后會彈出彈窗選擇檢測范圍,

點擊 OK 等待分析結果:

這個工具除了會檢測記憶體泄漏,還會檢測代碼是否規范、是否有沒用到的導包、可能的bug、安全問題等等,
3.2 Memory Profile
Memory Profile 的使用
3.3 LeakCanary
LeakCanary 的使用
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/297608.html
標籤:其他
