性能优化之内存泄露

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 activityStack;替换成private Stack> 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 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 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引发的内存泄露类似

你可能感兴趣的:(性能优化之内存泄露)