Android 性能优化实践(二)——内存抖动与内存泄漏

一、内存抖动

1.内存抖动的危害

由于垃圾回收机制老年代里面的标记清理算法,大有大量对象创建并快速销毁后,会在内存里面留下大量的内存碎片,这时如果有大对象需要申请内存时,就会产生OOM。

2.如何查看程序是否有内存抖动现象

可以利用Android studio 的profiler工具

在这里插入图片描述
当内存出现大量小幅度升降时,即可判断为内存抖动

3.比较常见造成内存抖动的场景

造成内存抖动的原因是在短时间内创建了大量的对象,所以我们特别应该避免在频繁调用的方法里面创建对象。

(1)字符串拼接
 String str = "一"+ "二"+"三"+"四";

在JVM将java代码翻译成字节码的时候,String对象的每次拼接都会创建StringBuilder对象。
优化方案:我们可以自己StringBuilder对象

StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("一");
stringBuilder.append("二");
stringBuilder.append("三");
stringBuilder.append("四");
(2)在循环里面创建对象

如果循环次数过多,直接在循环体里面创建对象可能需要慎重考虑,能在循环外面创建对象的尽量在外面创建。

(3)在onDraw 里面创建对象

onDraw也是调用比较频繁的函数,所以创建对象也需要慎重考虑,比如Paint、Path的创建可以在View初始化的时候就创建,如果要在onDraw里面对其改变,直接重置即可。

二、内存泄漏

内存泄漏是指程序中已分配的内存,由于某种原因,没有被释放或者是无法被释放,造成内存浪费的现象。主要是短生命周期对象强引用了长生命周期,导致短生命周期对象该被回收的时候无法被回收。

1.内存泄漏的危害

大量的内存泄漏会直接导致程序浪费大量的内存,从而导致系统OOM

2.如何查看程序中是否有内存泄漏

项目中主要的内存泄漏是Activity的内存泄漏,如果我们怀疑SecondActivity 存在内存泄漏,可以用 Android studio 的profiler工具和MAT工具查看。

我们在App里面频繁进出SecondActivity ,如果内存状态如下图所示持续增加,则说明可能存在内存泄漏,需要继续查找

在这里插入图片描述
这时需要点上门的下载按钮,将此时的内存状态dump下来。dump下来后,profiler会自动显示如下Heap Dump数据,我们以Arrange by package 排序,去对应包名下找我们检测的SecondActivity 对象是否存在,如下图所示,这里存在6个SecondActivity 对象以及6个SecondActivity 内部类对象。

在这里插入图片描述
看到这里我们只能知道内存中存在SecondActivity 对象,但并不代表就存在内存泄漏,因为内存泄漏是需要被其他对象强引用。所以我们需要借助另一个工具MAT。
我们先需要将当前看到的内存文件以hprof的格式导出来,直接在profiler窗口的右边对应的session 右键即可导出。由于Android studio 导出来的hprof文件并非标准hprof文件,所以需要先用借助Android sdk 安装目录下platform-tools目录下的hprof-conv.exe工具转换下。

hprof-conv -z D:\workspace\09.05\memory-20200905T142908.hprof  D:\workspace\09.05\1.hprof

备注:hprof-conv命令需要配置环境变量
用MAT 以open heap file 的方式打开转换后的1.hprof文件

在这里插入图片描述
如上图所示,点1处可以列出内存中所有对象,点2处可以输入想要查找的对象名称

在这里插入图片描述
查到对应的对象后,右键按右键选Merge Shortest Paths to GC Roots ——> exclude all phantom/weak/soft etc.references 去除掉虚引用、弱引用、和软引用。
这时如果还存在对象,就说明SecondActivity 确实存在内存泄漏
再点开过滤虚引用、弱引用、和软引用后还存在的对象,如下图所示
在这里插入图片描述

可以看出是系统InputMethodManager引起的内存泄漏。规避方案网上很多,这里就不详述了。

3.内存泄漏的常见场景

(1)将Activity 的context对象传入单例类

由于单例类的的对象经常是static的,而static对象可作为垃圾回收机制的GCRoots对象,也就是单例类的对象的生命周期远远高于Activity的生命周期,所以会造成内存泄漏
优化方案:先看看是否是必须传context,一般Activity 的context只在UI层传递即可。如果必须要传context对象,可以考虑传Application的context。

(2)匿名内部类的使用

举例


public class SecondActivity extends Activity {
    public static final String TAG = "SecondActivity";

    private Handler handler ;

    private void handleMsg(){
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
               
            }
        },2000);
    }
...
}

由于匿名内部类会含有外部类的引用对象,上面代码Runnable对象中就会引用SecondActivity 对象,所以会造成内存泄漏。
优化方案1:
在SecondActivity 的onDestroy方法中调用handler.removeCallbacksAndMessages(null)可从Handler 队列中移除所有的Runnable对象,从而打断引用链。

@Override
protected void onDestroy() {
    super.onDestroy();
    handler.removeCallbacksAndMessages(null);
}

优化方案2:
将Runnable写成静态内部类,静态内部类不会引用外部类的对象。如果静态内部类中需要用到外部类的对象,可以用弱引用传进去。对应代码如下:

public class SecondActivity extends Activity {
    public static final String TAG = "SecondActivity";

    private Handler handler ;
    private MyRunnable myRunnable = new MyRunnable(this);

    static class MyRunnable implements Runnable{
         WeakReference<SecondActivity> reference ;
         public MyRunnable(SecondActivity activity){
             reference = new WeakReference<SecondActivity>(activity);
         }

         @Override
         public void run() {
             SecondActivity secondActivity = reference.get();
              if(null != secondActivity ){
              //to do something
             }
         }
    }
    private void handleMsg(){
        handler.postDelayed(myRunnable,2000);
    }
    ...
}
(3)集合的使用

入股集合中的某个元素对象 后期不用了,需要把这个元素remove掉。

(4)监听器的使用

设置的监听器,在监听页面销毁的时候,记得把监听器清除掉。

(5)未释放资源

比如文件流未关闭,这里一定要保证在finally中把每个流单独try catch全都关掉,以防在前面关流过程中就发生了异常,后面的留就无法关闭了。

(6)系统bug

如WebView 、InputMethodManager

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值