性能优化之内存泄露

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引发的内存泄露类似

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容