内存泄露排查实战手记

来源:互联网 发布:java面向对象试题 编辑:程序博客网 时间:2024/05/22 14:10

from http://blog.csdn.net/sodino/article/details/17511677

问题现象:

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

问题期望:

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

问题排查:

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

1.静态实例长期占用JobAppInterface

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

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

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

严重,无法释放

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

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

原因:

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

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public static SettingManager getInstance(AppInterface app) {  
  2.     if (sInstance == null) {  
  3.         sInstance = new SettingManager(app);  
  4.     }  
  5.     return sInstance;  
  6. }  

解决方案:

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

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1.  public static SettingManager getInstance(AppInterface app) {  
  2.      if (sInstance == null) {  
  3.          sInstance = new SettingManager(app);  
  4.      } else if (sInstance.mApp != app) {  
  5.         //切换了账号  
  6. sInstance = null;  
  7. sInstance = new SettingManager(app);  
  8.   
  9.      return sInstance;  
  10.  }  

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

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. JobAppInterface.java  
  2. protected void onDestroy() {  
  3.     ... ...  
  4.     SettingManager.clearInstance(this);  
  5.     ... ...  
  6. }  
  7.   
  8. SettingManager.java  
  9. public static void clearInstance(AppInterface app){  
  10.     if(sInstance != null && sInstance.mApp == app){  
  11.         sInstance = null;  
  12.     }  
  13. }  


本文为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的地方,如下:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1.   JobInitHandler.java中:  
  2.   private void onStateRunning() {  
  3.     ... ...  
  4.     ... ...  
  5.     // 消息拉取完成后,做一些事情  
  6.        if(curStep == (STEP_GET_MSG + 1)){  
  7.         doSomethingAfterSyncMsg();//  ---->这里去关闭MessageThread..  
  8.        }  
  9.       
  10.     switch (curStep) {  
  11.        case STEP_INIT:  
  12.            // action...  
  13.            break;  
  14.        case STEP_START:  
  15.         // action...  
  16.            break;  
  17.         ... ...  
  18.         ... ...  
  19.     };  
  20. }  


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

解决方案:

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

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public void destroy() {  
  2.     ... ...  
  3.     ... ...  
  4.     // 停止代理处理线程  
  5.     app.getHandler().stopProxyThread();  
  6.     ... ...  
  7.     ... ...  
  8. }  

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

严重,无法释放

注册BroadcastReceiver后没有反注册

表现:

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


解决方案:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. if(tmpHandler != null && tmpHandler instanceof DataLineHandler){  
  2.     ((DataLineHandler)tmpHandler).close();// 执行反注册操作  
  3. }  

问题总结:

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

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

0 0
原创粉丝点击