美文网首页
性能优化之内存泄露

性能优化之内存泄露

作者: CP9 | 来源:发表于2018-01-19 15:14 被阅读32次

    Android Profiler

    注意:只支持5.0以后的Android设备

    1. 运行程序后,点击AS下方的工具栏 profile_sub.png

      中的Android Profiler

    2. 直接使用Android上方菜单栏的 profile_icon.png

      运行Android Profiler

    打开MEMORY工具

    1. 在Android Profiler界面选择你的应用的包名 选择你的应用包名.png
    2. 点击打开MEMORY工具


      profile_memory.png
    • ① 强制执行垃圾收集事件的按钮。
    • ② 捕获堆转储的按钮。
    • ③ 记录内存分配的按钮。
    • ④ 放大时间线的按钮。
    • ⑤ 跳转到实时内存数据的按钮。
    • ⑥ 事件时间线显示活动状态、用户输入事件和屏幕旋转事件。
    • ⑦ 内存使用时间表,其中包括以下内容:
      * 每个内存类别使用多少内存的堆栈图,如左边的y轴和顶部的颜色键所示。
      * 虚线表示已分配对象的数量,如右侧y轴所示。
      * 每个垃圾收集事件的图标。

    分析Heap Dump

    1. 点击图②按钮得到一个时间点的Heap Dump,如图 heap_dump.png
    2. 选择按照包名排序 选择排序.png
    3. 这里只关心你的包名下的类 heap_dump的你的包名.png

    导出hprof文件并在Android Studio中分析

    1. 点击Heap Dump的导出图标 export_icon.png

      导出hprof文件

    2. 将hprof文件拖到AS中查看,如图 AS中的hprof文件.png
    3. 点击右边的Analyzer Tasks按钮,分析是否内存泄露 执行AnalyzerTask之后.png

    使用hprof-conv转换成MAT能打开的文件

    MAT(Memory Analyzer Tool)的下载地址 https://www.eclipse.org/mat/

    hprof-conv这个工具在Andorid sdk的platform-tools文件夹下,因为这个文件夹下的工具经常有使用到,可以加入到系统环境变量中

    在命令和那个中执行以下命令:

    hprof-conv.png
    成功后会得到MAT能打开的文件leak_mat.hprof

    使用MAT查看调用栈定位内存泄露的原因

    1. 在MAT中打开文件leak_mat.hprof,会显示饼图如下: mat饼图.png
    2. 点击上图中的红色圈中的按钮,查看直方图,它默认直接显示当前内存中各种类型对象的数量及这些对象的shallow heapretained heap mat直方图.png
    3. 搜索内存泄露的类,并过滤掉软弱虚引用 mat直方图中搜索内存泄露的类.png
    4. 查看调用链,找到内存泄露的原因


      调用链.png

    常见的内存泄露原因

    具体代码详见:https://github.com/cl9/zero_performance_optimization

    集合引起的内存泄露

    1. 经过下列操作 集合引起的内存泄露.gif
    2. 在AS中分析的结果 执行AnalyzerTask之后.png
    3. 从AS中分析的结果可以看出,SetActivityXX对象会有好几个,在MAT中查看其调用链 调用链.png

    从调用链可以看出来,ActivityStackManager中的instance对象中的集合activityStack对象持有SetActivityXX的对象,结合代码来看,在SetActivityXX销毁后,并没有将集合activityStack中的activity移除

    解决内存泄露

    1. ActivityStackManager中集合activityStack对象保存的是Activity的强引用,可以考虑使用弱引用
      private Stack<Activity> activityStack;替换成private Stack<WeakReference<Activity>> activityStack;,这样就能保证在发生GC后,回收分配给Activity的内存
    2. 在基类BaseSetActivity中,每次销毁Activity的时候,将其从集合activityStack中移除
    @Override
    protected void onDestroy() {
        super.onDestroy();
        SolutionActivityStackManager.getAppManager().finishActivity(this);
    }
    
    1. 修改后,重新检测在MAT中如图: 修改后的mat直方图.png

      ,一般会可能会出现两种结果,一种是上图,所有的SetActivityXX在内存中的对象是0,还有一种是直方图中搜索不到SetActivityXX了

    单例引起的内存泄露

    1. 类似集合引起的内存泄露的操作
    2. 在AS中分析的结果 单例执行AnalyzerTask之后.png
    3. 从AS中分析的结果可以看出,SingletonActivity对象会有好几个,在MAT中查看其调用链 单例调用链.png

    从调用链可以看出来,ToastManager中的sInstance对象中持有SingletonActivity的对象,结合代码来看,在SingletonActivity销毁后,由于ToastManager的单例对象一直持有SingletonActivity的对象,所以SingletonActivity的对象不能正常回收引起内存泄露

    解决内存泄露

    1. ToastManager中的单例对象持有的是Context的强引用,可以考虑使用弱引用
      private Context mContext;替换成private WeakReference<Context> wrContext;,这样就能保证在发生GC后,回收分配给Context的内存
    2. 修改后,重新检测在MAT中如图: 单例修改后的mat直方图.png
      注意:这样做会出现一个问题,就是当退出SingletonActivity后,再次进入SingletonActivity,点击弹出Toast就会没反应,因为当SingletonActivity销毁后,弱引用得到的Context就为null了,就不会再弹出Toast
    补充方案 —— SolutionToastManager2
    1. ToastManager中的单例对象持有的mContext = context;替换成mContext = context.getApplicationContext();

    这样修改后就能同时解决内存泄露和上个方案出现的问题了

    Handler引起的内存泄露

    1. 类似集合引起的内存泄露的操作
    2. 在AS中分析的结果 handler执行AnalyzerTask之后.png
    3. 从AS中分析的结果可以看出,HandlerActivity对象会有好几个,在MAT中查看其调用链 handler调用链.png

    从调用链可以看出来,消息队列中等待处理的消息持有HandlerActivity的对象,结合代码来看,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当HandlerActivity退出时,消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有HandlerActivity的引用,所以导致HandlerActivity的内存资源无法及时回收,引发内存泄漏

    解决内存泄露

    1. 对Handler持有的对象使用弱引用,创建Handler时不要使用匿名类或者内部类,应该使用静态内部类
    public static class MyHandler extends Handler {
        WeakReference<Context> wrContext;
        public MyHandler(Context context) {
            this.wrContext = new WeakReference<>(context);
        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Context context = wrContext.get();
            if (context != null) {
                Toast.makeText(context, "成功获取到数据", Toast.LENGTH_SHORT).show();
            }
        }
    }
    
    1. 在Activity销毁后,移除消息队列中所有消息和所有的Runnable
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
    
    1. 修改后,重新检测后的结果: 修改后的handler的HeapDump.png

      ,内存中已经没有HandlerActivity对象了

    Thread引发的内存泄露

    这个和Handler引发的内存泄露类似

    相关文章

      网友评论

          本文标题:性能优化之内存泄露

          本文链接:https://www.haomeiwen.com/subject/nmzsoxtx.html