以前一直在簡書寫做,第一次發佈到SF上來,也是第一次使用SF,後面會盡可能同步到SF,更多文章請關注:
簡書 編程之樂
轉載請註明出處:謝謝!java
Java判斷對象是否能夠回收使用的而是可達性分析算法。android
在主流的商用程序語言中(Java和C#),都是使用可達性分析算法判斷對象是否存活的。這個算法的基本思路就是經過一系列名爲"GC Roots"的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的,下圖對象object5, object6, object7雖然有互相判斷,但它們到GC Roots是不可達的,因此它們將會斷定爲是可回收對象。git
在Java語言裏,可做爲GC Roots對象的包括以下幾種:github
a.虛擬機棧(棧楨中的本地變量表)中的引用的對象 b.方法區中的類靜態屬性引用的對象 c.方法區中的常量引用的對象 d.本地方法棧中JNI的引用的對象
摘自《深刻理解Java虛擬機》算法
關於LeakCanary使用參考如下文章:編程
LeakCanary的內存泄露提示通常會包含三個部分:
第一部分(LeakSingle類的sInstance變量)
引用第二部分(LeakSingle類的mContext變量),
致使第三部分(MainActivity類的實例instance)泄露.閉包
即便是空的Activity,若是檢測泄露時候遇到了以下這樣的泄露,注意,把refWatcher.watct()放在onDestroy裏面便可解決,或者忽略這樣的提示。
因爲文章已寫不少,下面的就再也不修改,忽略這種錯誤便可。app
* com.less.demo.TestActivity has leaked: * GC ROOT static android.app.ActivityThread.sCurrentActivityThread * references android.app.ActivityThread.mActivities * references android.util.ArrayMap.mArray * references array java.lang.Object[].[1] * references android.app.ActivityThread$ActivityClientRecord.activity * leaks com.less.demo.TestActivity instance
protected void onDestroy() { super.onDestroy(); RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(this); }
案例一(靜態成員引發的內存泄露)less
public class App extends Application { private RefWatcher refWatcher; @Override public void onCreate() { super.onCreate(); refWatcher = LeakCanary.install(this); } public static RefWatcher getRefWatcher(Context context) { App application = (App) context.getApplicationContext(); return application.refWatcher; } }
測試內部類持有外部類引用,內部類是靜態的(GC-ROOT,將一直連着這個外部類實例),靜態的會和Application一個生命週期,這會致使一直持有外部類引用(內部類隱含了一個成員變量$0), 即便外部類制空= null,也沒法釋放。ide
OutterClass
public class OutterClass { private String name; class Inner{ public void list(){ System.out.println("outter name is " + name); } } }
TestActivity
public class TestActivity extends Activity { // 靜態的內部類實例 private static OutterClass.Inner innerClass; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); OutterClass outterClass = new OutterClass(); innerClass = outterClass.new Inner(); RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(outterClass);// 監控的對象 outterClass = null; }
案例二(單例模式引發的內存泄露)
DownloadManager
public class DownloadManager { private static DownloadManager instance; private Task task ; public static DownloadManager getInstance(){ if (instance == null) { instance = new DownloadManager(); } return instance; } public Task newTask(){ this.task = new Task(); return task; } }
Task
public class Task { private Call call; public Call newCall(){ this.call = new Call(); return call; } }
Call
public class Call { public void execute(){ System.out.println("=========> execute call"); } }
TestActivity
public class TestActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); RefWatcher refWatcher = App.getRefWatcher(this); Task task = DownloadManager.getInstance().newTask(); Call call = task.newCall(); call.execute(); refWatcher.watch(call);// 監控的對象 call = null; // 沒法回收,DownloadManager是靜態單例,引用task,task引用了call,即便call置爲空,也沒法回收,切斷GC_ROOT 聯繫便可避免內存泄露,即置task爲空。 } }
部分日誌打印以下:當前的GC_ROOT是DownloadManager的instance實例。
In com.leakcanary.demo:1.0:1. * com.less.demo.Call has leaked: * GC ROOT static com.less.demo.DownloadManager.instance * references com.less.demo.DownloadManager.task * references com.less.demo.Task.call * leaks com.less.demo.Call instance
關於上面兩種方式致使的內存泄露問題,這裏再舉兩個案例說明以增強理解。
案例三(靜態變量致使的內存泄露)
public class TestActivity extends Activity { private static Context sContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); sContext = this; RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(this);// 監控的對象 }
打印日誌以下:
In com.leakcanary.demo:1.0:1. com.less.demo.TestActivity has leaked: GC ROOT static com.less.demo.TestActivity.sContext leaks com.less.demo.TestActivity instance
從這段日誌能夠分析出:聲明static後,sContext的生命週期將和Application同樣長,Activity即便退出到桌面,Application依然存在->sContext依然存在,GC此時想回收Activity卻發現Activity仍然被sContext(GC-ROOT鏈接着),致使死活回收不了,內存泄露。
上面的代碼改造一下,以下。
public class TestActivity extends Activity { private static View sView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); sView = new View(this); RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(this); } }
日誌以下
In com.leakcanary.demo:1.0:1. com.less.demo.TestActivity has leaked: GC ROOT static com.less.demo.TestActivity.sView references android.view.View.mContext leaks com.less.demo.TestActivity instance
案例四(單例模式致使的內存泄露)
DownloadManager
public class DownloadManager { private static DownloadManager instance; private List<DownloadListener> mListeners = new ArrayList<>(); public interface DownloadListener { void done(); } public static DownloadManager getInstance(){ if (instance == null) { instance = new DownloadManager(); } return instance; } public void register(DownloadListener downloadListener){ if (!mListeners.contains(downloadListener)) { mListeners.add(downloadListener); } } public void unregister(DownloadListener downloadListener){ if (mListeners.contains(downloadListener)) { mListeners.remove(downloadListener); } } }
TestActivity
public class TestActivity extends Activity implements DownloadManager.DownloadListener{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); DownloadManager.getInstance().register(this); RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(this); } @Override protected void onDestroy() { super.onDestroy(); // 忘記 unregister // DownloadManager.getInstance().unregister(this); } @Override public void done() { System.out.println("done!"); } }
In com.leakcanary.demo:1.0:1. * com.less.demo.TestActivity has leaked: * GC ROOT static com.less.demo.DownloadManager.instance * references com.less.demo.DownloadManager.mListeners * references java.util.ArrayList.array * references array java.lang.Object[].[0] * leaks com.less.demo.TestActivity instance
答案是:否認的。
以下案例,有限時間內是能夠挽救內存泄露發生的,因此控制好生命週期,其餘狀況:如單例對象(靜態變量)的生命週期比其持有的sContext
的生命週期更長時 ->內存泄露,更短時->躲過內存泄露。
public class TestActivity extends Activity { private static Context sContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); sContext = this; new Handler().postDelayed(new Runnable() { @Override public void run() { sContext = null; } },1000);// 分別測試1s和12s,前者不會內存泄露,後者必定泄露。因此若是趕在GC以前切斷GC_ROOT是能夠避免內存泄露的。 } @Override protected void onDestroy() { super.onDestroy(); RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(this); } }
handler致使的內存泄露可能咱們大多數都犯過.
注意如下代碼中的註釋,非靜態內部類雖然持有外部類引用,可是持有並不表明必定泄露,而是看gc時誰的命長。通過測試 狀況(1)始終沒有內存泄露。
爲何會這樣, 很早閱讀Handler源碼時候記得這幾個貨都是互相引用來引用去的,Message有個target字段, message.target = handler;
handler.post(message);又把這個message推入了MessageQueue中,而MessageQueue是在一個Looper線程中不斷輪詢處理消息,而有時候message仍是個老不死,可以重複利用。若是當Activity退出時候,還有消息未處理或正在處理,因爲message引用handler,handler又引用Activity,此時將引起內存泄露。
public class TestActivity extends Activity { private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); System.out.println("===== handle message ===="); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); // (1) 不會致使內存泄露 handler.sendEmptyMessageDelayed(0x123,0); // (2) 會致使內存泄露,非靜態內部類(包括匿名內部類,好比這個 Handler 匿名內部類)會引用外部類對象 this(好比 Activity) // 當它使用了 postDelayed 的時候,若是 Activity 已經 finish 了,而這個 handler 仍然引用着這個 Activity 就會導致內存泄漏 // 由於這個 handler 會在一段時間內繼續被 main Looper 持有,致使引用仍然存在. handler.sendEmptyMessageDelayed(0x123, 12000); } @Override protected void onDestroy() { super.onDestroy(); RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(this); } }
com.less.demo.TestActivity has leaked: * GC ROOT android.view.inputmethod.InputMethodManager$ControlledInputConnectionWrapper.mH * references com.android.internal.view.IInputConnectionWrapper$MyHandler.mQueue * references android.os.MessageQueue.mMessages * references android.os.Message.target , matching exclusion field android.os.Message#target * references com.less.demo.TestActivity$1.this$0 (anonymous subclass of android.os.Handler) * leaks com.less.demo.TestActivity instance
知道了原理後,即便寫出易於內存泄露的代碼也可以避免內存泄露啦。
上述代碼只需在onDestroy()函數中調用mHandler.removeCallbacksAndMessages(null);
在Activity退出的時候的移除消息隊列中全部消息和全部的Runnable。
內部類種類大體以下:
爲何非靜態內部類持有外部類引用,靜態內部類不持有外部引用。
這個問題很是簡單,就像 static的方法只能調用static的東西,非static能夠調用非static和static的同樣。static--> 針對class, 非static->針對 對象,我是這麼簡單理解的。看圖:
匿名內部類
將局部內部類的使用再深刻一步,假如只建立某個局部內部類的一個對象,就沒必要命名了。
匿名內部類的類型能夠是以下幾種方式。
匿名內部類總結:
- 其實主要就是類定義一次就失效了,主要使用的是這個類(不知道名字)的實例。根據內部類的特性,可以實現回調和閉包。
- JavaScript和Python的回調傳遞的是fuction,Java傳遞的是object。
Java中經常用到回調,而回調的本質就是傳遞一個對象,JavaScript或其餘語言則是傳遞一個函數(如Python,或者C,使用函數指針的方式),因爲傳遞一個對象能夠攜帶其餘的一些信息,因此Java認爲傳遞一個對象比傳遞一個函數要靈活的多(固然java也能夠用Method反射對象傳遞函數)。參考《Java核心技術》
非靜態內部類致使內存泄露(前提dog的命長)
下面的案例將致使內存泄露
其中private static Dog dog ;
致使Dog的命比TestActivity長,這就糟糕了,可是注意,若是改成private Dog dog ;
即便Dog是非靜態內部類,也不會致使內存泄露,要死也是Dog先死,畢竟Dog是TestActivity的家庭成員,開掛也得看主人。
public class TestActivity extends Activity { private static Dog dog ; class Dog { public void say(){ System.out.println("I am lovely dog!"); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); dog = new Dog(); } @Override protected void onDestroy() { super.onDestroy(); RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(this); } }
In com.leakcanary.demo:1.0:1. * com.less.demo.TestActivity has leaked: * GC ROOT static com.less.demo.TestActivity.dog * references com.less.demo.TestActivity$Dog.this$0 * leaks com.less.demo.TestActivity instance
哪些內部類或者回調函數是否持有外部類對象?
一個反射案例說明一切
/** * 做者: limitless * 描述: 一個案例測試全部類型內部類對外部類對象的持有狀況 * 網站: https://github.com/wangli0 */ public class Main { /* 持有外部類引用 */ private IAppListener mAppListener = new IAppListener() { private String name; @Override public void done() { System.out.println("匿名內部類對象做爲成員變量"); } }; /* 未持有 */ private static IAppListener sAppListener = new IAppListener() { private String name; @Override public void done() { System.out.println("匿名內部類對象做爲static成員變量"); } }; public static void main(String args[]) { Main main = new Main(); main.test1(); main.test2(); main.test3();// test3 《=》test4 main.test4(); main.test5(); main.test6(); } class Dog { private String name; } /* 持有外部類引用 */ public void test1(){ Dog dog = new Dog(); getAllFieldName(dog.getClass()); } static class Cat { private String name; } /* 未持有 */ private void test2() { Cat cat = new Cat(); getAllFieldName(cat.getClass()); } /* 持有外部類引用 */ private void test3() { class Monkey{ String name; } Monkey monkey = new Monkey(); getAllFieldName(monkey.getClass()); } /* 持有外部類引用 */ private void test4() { // 經常使用做事件回調的地方(有可能引發內存泄露) IAppListener iAppListener = new IAppListener() { private String name; @Override public void done() { System.out.println("匿名內部類"); } }; getAllFieldName(iAppListener.getClass()); } /* 持有外部類引用 */ private void test5() { getAllFieldName(mAppListener.getClass()); } /* 未持有 */ private void test6() { getAllFieldName(sAppListener.getClass()); } private void getAllFieldName(Class<?> clazz) { System.out.println("className: ======> " + clazz.getSimpleName()); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { System.out.println(field.getName()); } } }
上述結果足夠說明,即便是方法內的回調對象也是持有外部類引用的,那麼雖然做用域是局部的,也有存在內存泄露的可能。我屢次強調 持有某對象 不表明必定泄露,看的是誰命長。回調在Android開發過程當中幾乎到處可見,若是持有就會內存泄露的話,那幾乎就無法玩了。
通常狀況下,咱們經常設置某個方法內的xx.execute(new Listener(){xx});
是不會致使內存泄露的,這個方法執行完,局部做用域就失效了。可是若是在execute(listener);過程當中,某個單例模式的對象 忽然引用了這個listener對象,那麼就會致使內存泄露。
下面用實例證實個人想法
TestActivity
public class TestActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); Task task = new Task(); task.execute(new ICallback() { @Override public void done() { System.out.println("下載完成!"); } }); } @Override protected void onDestroy() { super.onDestroy(); RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(this); } }
Task
public class Task { public void execute(ICallback iCallback) { DownloadManager.getInstance().execute(iCallback); } }
DownloadManager
public class DownloadManager { public static DownloadManager instance; private ICallback mICallback; public static DownloadManager getInstance(){ if (instance == null) { instance = new DownloadManager(); } return instance; } public void execute(ICallback iCallback) { this.mICallback = iCallback;// 反例,千萬不要這麼作,將致使內存泄露,若是註釋掉這行,內存泄露不會發生 iCallback.done(); }
這足以證實個人想法是正確的,Callback的不巧當使用一樣會致使內存泄露 的發送。
public class TestActivity extends Activity { private MyHandler myHandler = new MyHandler(this); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); } static class MyHandler extends Handler { private WeakReference<Activity> mWeakReference; public MyHandler(Activity activity){ mWeakReference = new WeakReference<Activity>(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); Toast.makeText(mWeakReference.get(), "xxxx", Toast.LENGTH_LONG).show(); Log.d("xx", mWeakReference.get().getPackageName()); } } @Override protected void onDestroy() { super.onDestroy(); RefWatcher refWatcher = App.getRefWatcher(this); refWatcher.watch(this); } }