原标题:全方位带你彻底搞懂Android内存泄露 | 案例分析

本文作者

作者:编程之乐

链接:http://www.jianshu.com/p/efec4c77e265

本文由作者授权原创发布。

1

Java内存回收方式

Java判断对象是否可以回收使用的而是可达性分析算法。

在主流的商用程序语言中(Java和C#),都是使用可达性分析算法判断对象是否存活的。这个算法的基本思路就是通过一系列名为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,下图对象object5, object6, object7虽然有互相判断,但它们到GC Roots是不可达的,所以它们将会判定为是可回收对象。

91a91c64fe460fbf911cd28e053862c9.png

在Java语言里,可作为GC Roots对象的包括如下几种:

a.虚拟机栈(栈桢中的本地变量表)中的引用的对象

b.方法区中的类静态属性引用的对象

c.方法区中的常量引用的对象

d.本地方法栈中JNI的引用的对象

摘自《深入理解Java虚拟机》

2

使用leakcanary检测泄漏

关于LeakCanary使用参考以下文章:

LeakCanary: 让内存泄露无所遁形

http://www.liaohuqiu.net/cn/posts/leak-canary/

LeakCanary 中文使用说明

http://www.liaohuqiu.net/cn/posts/leak-canary-read-me/

LeakCanary的内存泄露提示一般会包含三个部分:

第一部分(LeakSingle类的sInstance变量)

引用第二部分(LeakSingle类的mContext变量),

导致第三部分(MainActivity类的实例instance)泄露.

1255f25f1fbd9d2604627178b302d7e7.png

leakcanary使用注意

即使是空的Activity,如果检测泄露时候遇到了如下这样的泄露,注意,把refWatcher.watct()放在onDestroy里面即可解决,或者忽略这样的提示。

由于文章已写很多,下面的就不再修改,忽略这种错误即可。

*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

protectedvoidonDestroy(){

super.onDestroy();

RefWatcher refWatcher = App.getRefWatcher( this);

refWatcher.watch( this);

}

3

leakcanary和代码示例说明内存泄露

案例一(静态成员引起的内存泄露)

publicclassAppextendsApplication{

privateRefWatcher refWatcher;

@Override

publicvoidonCreate(){

super.onCreate();

refWatcher = LeakCanary.install( this);

}

publicstaticRefWatcher getRefWatcher(Context context){

App application = (App) context.getApplicationContext();

returnapplication.refWatcher;

}

}

测试内部类持有外部类引用,内部类是静态的(GC-ROOT,将一直连着这个外部类实例),静态的会和Application一个生命周期,这会导致一直持有外部类引用(内部类隐含了一个成员变量$0), 即使外部类制空= null,也无法释放。

OutterClass

publicclassOutterClass{

privateString name;

classInner{

publicvoidlist(){

System. out.println( "outter name is "+ name);

}

}

}

TestActivity

publicclassTestActivityextendsActivity{

// 静态的内部类

privatestaticOutterClass.Inner innerClass;

@Override

protectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_test);

OutterClass outterClass = newOutterClass();

innerClass = outterClass.new Inner();

RefWatcher refWatcher = App.getRefWatcher( this);

refWatcher.watch(outterClass); // 监控的对象

outterClass = null;

}

6c02dee57720e5bbd3caf71af36456af.png

案例二(单例模式引起的内存泄露)

DownloadManager

publicclassDownloadManager{

privatestaticDownloadManager instance;

privateTask task ;

publicstaticDownloadManager getInstance(){

if(instance == null) {

instance = newDownloadManager();

}

returninstance;

}

publicTask newTask(){

this.task = newTask();

returntask;

}

}

Task

publicclassTask{

privateCall call;

publicCall newCall(){

this.call = newCall();

returncall;

}

}

Call

publicclassCall{

publicvoidexecute(){

System. out.println( "=========> execute call");

}

}

TestActivity

publicclassTestActivityextendsActivity{

@Override

protectedvoidonCreate(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为空。

}

}

ddba1316837531c17cd4524e923f5657.png

部分日志打印如下:当前的GC_ROOT是DownloadManager的instance实例。

Incom.leakcanary.demo:1.0:1.

* com.less.demo.Callhasleaked:

* GCROOTstaticcom.less.demo.DownloadManager.instance

* referencescom.less.demo.DownloadManager.task

* referencescom.less.demo.Task.call

* leakscom.less.demo.Callinstance

关于上面两种方式导致的内存泄露问题,这里再举两个案例说明以加强理解。

案例三(静态变量导致的内存泄露)

publicclassTestActivityextendsActivity{

privatestaticContext sContext;

@Override

protectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_test);

sContext = this;

RefWatcher refWatcher = App.getRefWatcher( this);

refWatcher.watch( this); // 监控的对象

}

}

8e9fad2a96b13c81d862fa05e7ac0708.png

打印日志如下:

Incom.leakcanary.demo:1.0:1.

com.less.demo.TestActivityhasleaked:

GCROOTstaticcom.less.demo.TestActivity.sContext

leakscom.less.demo.TestActivityinstance

从这段日志可以分析出:声明static后,sContext的生命周期将和Application一样长,Activity即使退出到桌面,Application依然存在->sContext依然存在,GC此时想回收Activity却发现Activity仍然被sContext(GC-ROOT连接着),导致死活回收不了,内存泄露。

上面的代码改造一下,如下。

publicclassTestActivityextendsActivity{

privatestaticView sView;

@Override

protectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_test);

sView = newView( this);

RefWatcher refWatcher = App.getRefWatcher( this);

refWatcher.watch( this);

}

}

d06fc742c35689bcb048442f097e6343.png

日志如下

Incom.leakcanary.demo:1.0:1.

com.less.demo.TestActivityhasleaked:

GCROOTstaticcom.less.demo.TestActivity.sView

referencesandroid.view.View.mContext

leakscom.less.demo.TestActivityinstance

案例四(单例模式导致的内存泄露)

DownloadManager

publicclassDownloadManager{

privatestaticDownloadManager instance;

privateList mListeners = newArrayList<>();

publicinterfaceDownloadListener{

voiddone();

}

publicstaticDownloadManager getInstance(){

if(instance == null) {

instance = newDownloadManager();

}

returninstance;

}

publicvoidregister(DownloadListener downloadListener){

if(!mListeners.contains(downloadListener)) {

mListeners. add(downloadListener);

}

}

publicvoidunregister(DownloadListener downloadListener){

if(mListeners.contains(downloadListener)) {

mListeners. remove(downloadListener);

}

}

}

TestActivity

publicclassTestActivityextendsActivityimplementsDownloadManager.DownloadListener{

@Override

protectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_test);

DownloadManager.getInstance().register( this);

RefWatcher refWatcher = App.getRefWatcher( this);

refWatcher.watch( this);

}

@Override

protectedvoidonDestroy(){

super.onDestroy();

// 忘记 unregister

// DownloadManager.getInstance().unregister(this);

}

@Override

public void done() {

System.out.println("done!");

}

}

fd95d91fc388d429cf500b1fe9e2e934.png

Incom.leakcanary.demo:1.0:1.

* com.less.demo.TestActivityhasleaked:

* GCROOTstaticcom.less.demo.DownloadManager.instance

* referencescom.less.demo.DownloadManager.mListeners

* referencesjava.util.ArrayList.array

* referencesarrayjava.lang.Object[]. [0]

* leakscom.less.demo.TestActivityinstance

错误写法一定导致内存泄露吗?

答案是:否定的。

如下案例,有限时间内是可以挽救内存泄露发生的,所以控制好生命周期,其他情况:如单例对象(静态变量)的生命周期比其持有的sContext

的生命周期更长时 ->内存泄露,更短时->躲过内存泄露。

publicclassTestActivityextendsActivity{

privatestaticContext sContext;

@Override

protectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_test);

sContext = this;

//分别测试1s和12s,前者不会内存泄露,后者一定泄露。所以如果赶在GC之前切断GC_ROOT是可以避免内存泄露的。

newHandler().postDelayed( newRunnable() {

@Override

publicvoidrun(){

sContext = null;

}

}, 1000);

super.onDestroy();

RefWatcher refWatcher = App.getRefWatcher( this);

refWatcher.watch( this);

}

}

4

Handler 引起的内存泄露详细分析

handler导致的内存泄露可能我们大多数都犯过.

注意以下代码中的注释,非静态内部类虽然持有外部类引用,但是持有并不代表一定泄露,而是看gc时谁的命长。经过测试 情况(1)始终没有内存泄露。

为什么会这样, 很早阅读Handler源码时候记得这几个货都是互相引用来引用去的,Message有个target字段, message.target = handler;

handler.post(message);又把这个message推入了MessageQueue中,而MessageQueue是在一个Looper线程中不断轮询处理消息,而有时候message还是个老不死,能够重复利用。如果当Activity退出时候,还有消息未处理或正在处理,由于message引用handler,handler又引用Activity,此时将引发内存泄露。

publicclassTestActivityextendsActivity{

privateHandler handler = newHandler() {

@Override

publicvoidhandleMessage(Message msg){

super.handleMessage(msg);

System.out.println( "===== handle message ====");

}

};

@Override

protectedvoidonCreate(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);

}

}

4090c743db7babe3a2bc5a3e01f79db2.png

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。

5

内部类引起的内存泄露

内部类种类大致如下:

1. 非静态内部类(成员内部类)

2. 静态内部类(嵌套内部类)

3. 局部内部类(定义在方法内或者作用域内的类,好似局部变量,所以不能有访问控制符和static等修饰)

4. 匿名内部类(没有名字,仅使用一次new个对象即扔掉类的定义)

为什么非静态内部类持有外部类引用,静态内部类不持有外部引用。

这个问题非常简单,就像 static的方法只能调用static的东西,非static可以调用非static和static的一样。static--> 针对class, 非static->针对 对象,我是这么简单理解的。看图:

7851a1496b50d79c31b0d7b823c522f2.png

匿名内部类

将局部内部类的使用再深入一步,假如只创建某个局部内部类的一个对象,就不必命名了。

匿名内部类的类型可以是如下几种方式。

1. 接口匿名内部类

2. 抽象类匿名内部类

3. 类匿名内部类

匿名内部类总结:

1. 其实主要就是类定义一次就失效了,主要使用的是这个类(不知道名字)的实例。根据内部类的特性,能够实现回调和闭包。

2. Java和Python的回调传递的是fuction,Java传递的是object。

Java中常常用到回调,而回调的本质就是传递一个对象,Java或其他语言则是传递一个函数(如Python,或者C,使用函数指针的方式),由于传递一个对象可以携带其他的一些信息,所以Java认为传递一个对象比传递一个函数要灵活的多(当然java也可以用Method反射对象传递函数)。参考《Java核心技术》

非静态内部类导致内存泄露(前提dog的命长)

下面的案例将导致内存泄露

其中private static Dog dog ; 导致Dog的命比TestActivity长,这就糟糕了,但是注意,如果改为private Dog dog ; 即使Dog是非静态内部类,也不会导致内存泄露,要死也是Dog先死,毕竟Dog是TestActivity的家庭成员,开挂也得看主人。

publicclassTestActivityextendsActivity{

privatestaticDog dog ;

classDog{

publicvoidsay(){

System.out.println( "I am lovely dog!");

}

}

@Override

protectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_test);

dog = newDog();

}

@Override

protectedvoidonDestroy(){

super.onDestroy();

RefWatcher refWatcher = App.getRefWatcher( this);

refWatcher.watch( this);

}

}

8a5e9fe2af73e2cfa4dd4b5853cb5565.png

Incom.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

*/

publicclassMain{

/* 持有外部类引用 */

privateIAppListener mAppListener = newIAppListener() {

privateString name;

@ Override

publicvoiddone(){

System. out.println( "匿名内部类对象作为成员变量");

}

};

/* 未持有 */

privatestaticIAppListener sAppListener = newIAppListener() {

privateString name;

@ Override

publicvoiddone(){

System. out.println( "匿名内部类对象作为static成员变量");

}

};

publicstaticvoidmain(String args[]){

Main main = newMain();

main.test1();

main.test2();

main.test3(); /* test3 《=》test4*/

main.test4();

main.test5();

main.test6();

}

classDog{

privateString name;

}

/* 持有外部类引用 */

publicvoidtest1(){

Dog dog = newDog();

getAllFieldName(dog.getClass());

}

staticclassCat{

privateString name;

}

/* 未持有 */

privatevoidtest2(){

Cat cat = newCat();

getAllFieldName(cat.getClass());

}

/* 持有外部类引用 */

privatevoidtest3(){

classMonkey{

String name;

}

Monkey monkey = newMonkey();

getAllFieldName(monkey.getClass());

}

/* 持有外部类引用 */

privatevoidtest4(){

/* 常用作事件回调的地方(有可能引起内存泄露)*/

IAppListener iAppListener = newIAppListener() {

privateString name;

@ Override

publicvoiddone(){

System. out.println( "匿名内部类");

}

};

getAllFieldName(iAppListener.getClass());

}

/* 持有外部类引用 */

privatevoidtest5(){

getAllFieldName(mAppListener.getClass());

}

/* 未持有 */

privatevoidtest6(){

getAllFieldName(sAppListener.getClass());

}

privatevoidgetAllFieldName(Class> clazz){

System. out.println( "className: ======> "+ clazz.getSimpleName());

Field[] fields = clazz.getDeclaredFields();

for(Field field : fields) {

System. out.println(field.getName());

}

}

}

69e7784a55482f2b8ca8b254e8d8a48c.png

上述结果足够说明,即使是方法内的回调对象也是持有外部类引用的,那么虽然作用域是局部的,也有存在内存泄露的可能。我多次强调 持有某对象 不代表一定泄露,看的是谁命长。回调在Android开发过程中几乎处处可见,如果持有就会内存泄露的话,那几乎就没法玩了。

一般情况下,我们常常设置某个方法内的xx.execute(new Listener(){xx});是不会导致内存泄露的,这个方法执行完,局部作用域就失效了。但是如果在execute(listener);过程中,某个单例模式的对象 突然引用了这个listener对象,那么就会导致内存泄露。

下面用实例证明我的想法

TestActivity

publicclassTestActivityextendsActivity{

@Override

protectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_test);

Task task = newTask();

task.execute( newICallback() {

@Override

publicvoiddone(){

System.out.println( "下载完成!");

}

});

}

@Override

protectedvoidonDestroy(){

super.onDestroy();

RefWatcher refWatcher = App.getRefWatcher( this);

refWatcher.watch( this);

}

}

Task

publicclassTask{

publicvoidexecute(ICallback iCallback){

DownloadManager.getInstance().execute(iCallback);

}

}

DownloadManager

publicclassDownloadManager{

publicstaticDownloadManager instance;

privateICallback mICallback;

publicstaticDownloadManager getInstance(){

if(instance == null) {

instance = newDownloadManager();

}

returninstance;

}

publicvoidexecute(ICallback iCallback){

this.mICallback = iCallback; /* 反例,千万不要这么做,将导致内存泄露,如果注释掉这行,内存泄露不会发生*/

iCallback.done();

}

b6b35b4cb384f2c4f3b3714c9886b812.png

这足以证明我的想法是正确的,Callback的不巧当使用同样会导致内存泄露 的发送。

总结

1. 如果某些单例需要使用到Context对象,推荐使用Application的context,不要使用Activity的context,否则容易导致内存泄露。单例对象的生命周期和Application一致,这样Application和单例对象就一起销毁。

2. 优先使用静态内部类而不是非静态的,因为非静态内部类持有外部类引用可能导致垃圾回收失败。如果你的静态内部类需要宿主Activity的引用来执行某些东西,你要将这个引用封装在一个WeakReference中,避免意外导致Activity泄露,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收 只被弱引用关联的对象,只被说明这个对象本身已经没有用处了。

publicclassTestActivityextendsActivity{

privateMyHandler myHandler = newMyHandler( this);

@Override

protectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_test);

}

staticclassMyHandlerextendsHandler{

privateWeakReference mWeakReference;

publicMyHandler(Activity activity){

mWeakReference = newWeakReference(activity);

}

@Override

publicvoidhandleMessage(Message msg){

super.handleMessage(msg);

Toast.makeText(mWeakReference.get(), "xxxx", Toast.LENGTH_LONG).show();

Log.d( "xx", mWeakReference.get().getPackageName());

}

}

@Override

protectedvoidonDestroy(){

super.onDestroy();

RefWatcher refWatcher = App.getRefWatcher( this);

refWatcher.watch( this);

}

}

责任编辑:

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐