[Android]内存泄露排查实战手记

内存泄露排查实战手记

Time:2013.09.02

Author:sodino

问题现象:

这里内存泄露是指已实例化的对象长期被hold住且无法释放或不能按照对象正常的生命周期进行释放。

问题期望:

进行多次重复操作后,能够正常回收该对象(JobAppInterface)。期望在切换帐号后,之前的JobAppInterface能够及时回收(允许等待一段时间后再回收)

问题排查:

经过排查,总结为三种情况导致JobAppInterface内存泄露:

1.静态实例长期占用JobAppInterface

2.线程没有被stop导致JobAppInterface无法释放。

3.Observer/Listener没有被反注册导致ActivityJobAppInterface无法被释放。

一号坑:静态实例长期占用

严重,无法释放

SettingManager的静态实例长期占据第一次初始化的JobAppInterface

表现:启动应用时所创建的JobAppInterface会一直被引用,无法释放。

原因:

经全文查找,发现其静态实例只有初始化的入口,条件是当其实例为null的时候就初始化。sInstance执行new操作后,再没有任何回收操作。这样,除非手Q完全退出了,不然sInstance会一直存在并且占其初始化时使用的JobAppInterface。

	public static SettingManager getInstance(AppInterface app) {
		if (sInstance == null) {
			sInstance = new SettingManager(app);
		}
		return sInstance;
	}

解决方案:

在获取实例时,如果sInstance已经存在,则对比app,当app不一致时则进行回收和新的实例的生成。

    public static SettingManager getInstance(AppInterface app) {
        if (sInstance == null) {
            sInstance = new SettingManager(app);
        } else if (sInstance.mApp != app) {
        	//切换了账号
			sInstance = null;
			sInstance = new SettingManager(app);
		}
        return sInstance;
    }

另,将该实例的回收与JobAppInterface的生命周期保持一致。在JobAppInterface.onDestory()时及时清理掉。

JobAppInterface.java
protected void onDestroy() {
	... ...
	SettingManager.clearInstance(this);
	... ...
}

SettingManager.java
public static void clearInstance(AppInterface app){
	if(sInstance != null && sInstance.mApp == app){
		sInstance = null;
	}
}


本文为Sodino所有,转载请注明出处:http://blog.csdn.net/sodino/article/details/17511677


二号坑:线程长期running导致对象无法被释放

严重,无法释放

MessageThread线程一直在运行,导致无法释放JobAppInterface

表现:

这个在疯狂乱点切换帐号时出现。MAT工具发现无法释放的JobAppInterface都有被多个MessageThread引用着。嫌疑很大。但当时对这块逻辑不熟,为了进一步确认,做了如下操作:

1.在生成MessageThread的地方将新new出现的Thread命名为"index_System.time",并在其start()之后输出相应的start日志,日志信息包括Threadname及其所关联的JobAppInterface.hashcode

2.在Thread.run()方法结尾处输出"exit"日志,日志信息同样包括Threadname及其所关联的JobAppInterface.hashcode

经比较,发现快速切换帐号后,每切换一次会新生成5Thread,但最后都只有5Thread会执行"exit"操作,则仍然为(n -1)*5Thread仍在运行着,导致JobAppInterface无法释放。

原因:

确定了MessageThread有问题后,可以下定决心分析下原因了。经查,发现在原逻辑中,是有关闭这些Thread的地方,如下:

   JobInitHandler.java中:
   private void onStateRunning() {
		... ...
		... ...
		// 消息拉取完成后,做一些事情
        if(curStep == (STEP_GET_MSG + 1)){
        	doSomethingAfterSyncMsg();//  ---->这里去关闭MessageThread..
        }
		
		switch (curStep) {
        case STEP_INIT:
            // action...
            break;
        case STEP_START:
        	// action...
            break;
			... ...
			... ...
		};
	}


在JobInitHandler.onStateRunning()"当消息拉取完成后,做一些事情"这里会去把MessageThread 停止掉。但问题就出在这里,关闭的时机出现了问题,在快速切换帐号的操作中,由于没有足够的时间让消息拉取操作完成,也就造成了curStep无法走到值为"(STEP_GET_MSG + 1)"的情形,导致MessageThread一直在空跑无法,其引用的JobAppInterface无法被释放。

解决方案:

在JobInitHandler.destory()时将仍在运行的Thread停止掉。

	public void destroy() {
		... ...
		... ...
		// 停止代理处理线程
		app.getHandler().stopProxyThread();
		... ...
		... ...
	}

三号坑:Observer/Listener没有被反注册

严重,无法释放

注册BroadcastReceiver后没有反注册

表现:

直接看图吧,new的一个BroadcastReceiver在构造函数中直接register,但通篇没有被ungister导致内存泄露。见图1


解决方案:

	if(tmpHandler != null && tmpHandler instanceof DataLineHandler){
		((DataLineHandler)tmpHandler).close();// 执行反注册操作
	}

问题总结:

就目前来说,经过以上三种类型问题的排查,目前已经达成目的。

这里小总结一下,泄露的原因分别为静态实例占用、线程没被及时停止、注册的Observer/BroadcastReceiver没有及时被反注册。但解决的方法都是同样的:在JobAppInterface的onDesotry()方法(或相似的如JobInitHandler.destory())中及时执行关停回收操作即可避免。



  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值