内存泄露排查原因及解决方法——内存优化(五)

转载请标明出处:http://blog.csdn.net/xx326664162/article/details/50084373 文章出自:薛瑄的博客

你也可以查看我的其他同类文章,也会让你有一定的收货!

示例代码

public class NonStaticNestedClassLeakActivity extends ActionBarActivity {

  TextView textView;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_non_static_nested_class_leak);
    textView = (TextView)findViewById(R.id.textview);
    Handler handler = new Handler();

    handler.postDelayed(new Runnable() {
      @Override public void 
        textView.setText("Done");
      }//a mock for long time work
    }, 800000L);


  }
}

打开MAT,进行分析

非静态内部类惹的祸

MAT是对java heap中变量分析的一个工具,它可以用于分析内存泄露。

  • 点击OQL图标
  • 在窗口输入select * from instanceof android.app.Activity并按Ctrl + F5或者!按钮(这个操作和在这篇文章的第三步操作的结果是一样)
  • 奇迹出现了,现在你发现泄露了许多的activity

我们来分析一下为什么GC没有回收它

这里写图片描述
图一

在OQL(Object Query Language)窗口下输入的查询命令可以获得所有在内存中的Activities

点击一个activity对象,右键选中Path to GC roots

这里写图片描述

图二

注意:这里红色方框中左下方的黄色小点,代表有GC Root
这里写图片描述

在打开的新窗口中,可以发现三个GC Root,

第一个是弱引用可以选中在左边的信息中查看

第二个GC Root 消息队列:

一旦你把Runnable或者Message发送到Handler中,它就会被放入LooperThread的消息队列,并且被保持引用,直到Message被处理。发送postDelayed这样的消息,延迟多少秒,它就会泄露至少多少秒。而发送没有延迟的消息的话,当队列中的消息过多时,也会造成临时的泄露。

第三个GC Root 主线程:

  • Activity是被this$0 所引用的,它实际上是匿名类对当前类的引用。
  • this$0引用callback
  • 接着callback又被Message中一串的next所引用,最后到主线程(< java local>)才结束。

任何情况下你在class中创建非静态内部类,内部类会(自动)拥有对当前类的一个强引用。

后两个GC Root都是由postDelayed()发送消息延迟造成的

解决方法:

1、尝试使用static inner class来解决

现在把Runnable变成静态的class

public class MainActivity extends AppCompatActivity {

    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.textView);
        Handler handler = new Handler();
        handler.postDelayed(new DoRunable(textView),900000);


    }

    private static final class DoRunable implements Runnable {
        private TextView textView;
        public DoRunable(TextView textView) {
            this.textView = textView;
        }

        @Override
        public void run() {
            textView.setText("Done");

        }
    }
}

摇一摇手机,在Android studio中触发垃圾回收,导出内存

下面三幅图中,请注意红色方框中的内容,这是在图一的查询结果上,右键选择菜单项的结果,比如:图二中的选择,跳出新的界面,将鼠标放到标题栏上,将会处在这个小提示(标题栏的完整描述)

  1. 选择Path to GC Roots ->With all references
    这里写图片描述

  2. 选择Merge Shortest Paths to GC Roots. -> exclude all weak/soft references
    这里写图片描述

  3. 选择Merge Shortest Paths to GC Roots. ->With all references
    这里写图片描述

比较三幅图发现,结果都不一样,原因如下:

TablesAre
Path to GC Roots-with all references从GC Roots节点到该对象的引用路径,包含所有引用类型
Merge Shortest Paths to GC Roots -> exclude all weak/soft references从该对象到GC Roots节点的最短引用路径,去除所有弱引用,其他类似
Merge Shortest Paths to GC Roots-with all references从该对象到GC Roots节点最短引用路径,包含所有引用类型

图一是所有的引用

图二是从该对象到GC Roots节点的最短引用路径,但是过滤掉弱引用和软引用,因为图三所示的引用属于弱引用,所有被过滤掉

图三是从该对象到GC Roots节点的最短引用路径,不过滤任何引用,在这里显示所有引用中,路径最短的一个,所以显示图中的情形

按道理说,在导出内存前,手动触发垃圾回收,应该不会有弱引用,但不知道为何这里依旧存在,估计是要多次触发。

在图二中,发现对象被textView引用了mContext,该引用是强引用,所以不能回收。

原作者的图,是选择了Merge Shortest Paths to GC Roots ->exclude all phantim/weak/soft etc. references

这里写图片描述

2、使用弱引用 + static Runnable

现在我们把刚刚内存泄露的罪魁祸首 - TextView改成弱引用。

这里写图片描述

  • 再次注意我们对TextView保持的是弱引用,现在让它运行,摇晃手机,不手动触发GC
    这里写图片描述

  • 再次注意我们对TextView保持的是弱引用,现在让它运行,摇晃手机,手动触发GC
    这里写图片描述

使用Merge Shortest Paths to GC Roots ->exclude all phantim/weak/soft etc. references,查看这两个对象
这里写图片描述

这里写图片描述

但为什么还是有两个实例,我也不明白,求高人指教

我们应该记住:

  • 使用静态内部类
  • Handler/Runnable的依赖要使用弱引用。

当把postDelayed设置为一个短时间,比如50ms的情况下,写这么多代码就有点亏了。还有一个更简单的方法,如下。

3、onDestroy中手动控制声明周期

Handler可以使用removeCallbacksAndMessages(null),它将移除这个Handler所拥有的RunnableMessage

在第二种的基础上,加入下面代码:

//Fixed by manually control lifecycle
  @Override protected void onDestroy() {
    super.onDestroy();
    myHandler.removeCallbacksAndMessages(null);
  }

横竖摇动手机,没有手动触发GC机制,依然是会存在很多实例,但是可以被GC回收,说明不是泄露

横竖摇动手机,手动触发GC机制,导出内存

这里得到的结果,依然是两个实例,原作者的结果是一个实例

这里写图片描述

使用Merge Shortest Paths to GC Roots ->exclude all phantim/weak/soft etc. references,查看这两个对象
这里写图片描述

这里写图片描述

这两个实例不是自己写的代码,所导致的泄露,所以该方法可行。

看到这两个实例,都是对系统view的引用,至于为什么会出现这样,请高人指点一下

结论

在Handler中使用postDelayed需要额外的注意,为了解决问题,我们有三种方法

  • 使用静态内部Handler/Runnable + 弱引用
  • 在onDestory的时候,手动清除Message
  • 使用Badoo开发的第三方的 WeakHandler

这三种你可以任意选用,第二种看起来更加合理,但是需要额外的工作。第三种方法是我最喜欢的,当然你也要注意WeakHandler不能与外部的强引用共同使用。


内存泄露的8种情况:https://www.jianshu.com/p/ac00e370f83d
以及对应的解决方法:https://www.jianshu.com/p/c5ac51d804fa

转载:http://www.jianshu.com/p/c49f778e7acf

关注我的公众号,轻松了解和学习更多技术
这里写图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

薛瑄

文章不错,请博主吃包辣条

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值