以前一直在简书写做,第一次发布到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); } }