Android性能优化(二)-内存优化

1、常见内存问题

常见的内存问题主要分为三种:java

一、内存抖动

指在短期内有大量的对象被建立或者被回收的现象android

危害:gc频繁,gc时会发生stw,致使卡顿git

二、内存泄漏

指程序在申请内存后,没法释放已申请的内存空间github

危害:一次内存泄漏彷佛不会有很大影响,但内存泄漏堆积后的后果就是内存溢出,致使崩溃。即便没发生内存溢出,占据较多内存后,会引起gc,致使卡顿数据库

三、内存溢出

指程序运行要用到的内存大于能提供的最大内存,程序运行不了的现象markdown

危害:程序没法运行app

2、经常使用的内存分析工具

一、Memory Profiler

AndroidStudio提供的内存分析工具,经常使用于直观判断是否有内存抖动和内存泄漏dom

如图所示,能够直观的看出来内存的波动,从而判断是否有内存问题ide

二、Mat

Mat能够用于深度分析内存抖动和内存泄漏工具

使用方法以下:

一、先用Memory Profiler的堆转储功能,记录下一段时间内的内存状况,存储为hprof文件。

二、使用hprof-conv工具,转换从Memory Profiler保存下来的hprof文件。由于Mat没法解析直接从Memory Profiler保存下来的文件,可是Android官方提供了转换工具hprof-conv。hprof-conv在安卓SDK中的platform-tools目录中。使用命令以下

hprof-conv 旧hprof文件 转换后的hprof文件

三、使用Mat解析转换后的hprof文件

点击下图该按钮打开hprof文件

四、Mat的基本功能

查看某个类引用相关

  • with incoming references : 表示该类被 哪些外部对应 引用了
  • with outgoing references : 表示 该类 持有了 哪些外部对象的引用

查看GC Root

在dominator_tree中,查找Activity->Path To GC Roots->with all references

在本案例中,结果以下:

结果出了系统类引用外,Activity被单例PluginManager给引用了,形成了泄漏,Activity没法释放

三、LeakCanary

LeakCanary是一个自动化的内存泄漏分析工具

使用方法

一、增长依赖

compile 'com.squareup.leakcanary:leakcanary-android:1.6.1'
复制代码

二、Application中开启检测功能

public class MyApplication extends Application {


    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        LeakCanary.install(this);
    }
}
复制代码

三、操做app,若是有泄漏则会通知出如今手机通知栏中,打开后,可看到如下这种内存泄漏的界面

LeakCanary原理

一、RefWatcher.watch() 建立一个 KeyedWeakReference 到要被监控的对象。

二、而后在后台线程检查引用是否被清除,若是没有,调用GC。

三、若是引用仍是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。

四、在另一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。

五、得益于惟一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄露。

六、HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并肯定是不是泄露。若是是的话,创建致使泄露的引用链。

七、引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展现出来。

判断对象是否能被回收,是使用弱引用结合引用队列实现,当一个对象能被回收,则会被加入到关联的引用队列中。以此来判断对象是否泄漏

下面的代码是参照LeakCanary检测是否泄露的源码所写的一段检测代码,基本能够体现出leakCanary的泄露检测原理

class RefWatcher {

    private val referenceQueue = ReferenceQueue<Any>()

    private val retainedKeys = HashSet<String>()

    private val gcTrigger = GcTrigger.Default

    private val executor = Executors.newSingleThreadExecutor()

    /** * 在Activity destroy时,执行判断是否泄露 */
    fun watch(obj : Any) {
        val key = UUID.randomUUID().toString()
        retainedKeys.add(key)
        val reference = KeyedWeakReference(key, obj, referenceQueue)
        executor.execute {
            //清除被加到队列中的对象
            removeReachableReferences()
            //第一次判断
            if(gone(reference)) {
                //没有泄露
            } else {
                //跑一遍gc,gc的同时会让线程挂起100ms
                gcTrigger.runGc()
                //再清除一次被加入ReferenceQueue中的引用
                removeReachableReferences()
                //再判断一次
                if(gone(reference)) {
                    //没有泄露
                } else{
                    //判断为泄露
                }
            }
            
        }
    }

    /** * 当一个对象能够被gc回收时,会被加入referenceQueue * 被加入到referenceQueue的引用,则断定对象不会泄露,从retainsKey中移除 */
    private fun removeReachableReferences() {
        var ref  = referenceQueue.poll() as KeyedWeakReference
        while(ref != null) {
            retainedKeys.remove(ref.key)
            ref = referenceQueue.poll() as KeyedWeakReference
        }
    }

    /** * retainedKeys中不包含的,则断定为可回收,不会泄露 */
    private fun gone(reference: KeyedWeakReference) : Boolean {
        return !retainedKeys.contains(reference.key)
    }

}
复制代码

四、Matrix的ResourceCanary

ResourceCanary是腾讯的APM中检测内存泄漏的模块,该模块实现和leakCanary十分接近,有两个点不一样:

一、leakCanary在二次判断前,执行gc,后线程挂起100秒,由于调用gc后,虚拟机不必定立刻执行gc,但这种判断方法存在误判的可能,可能确实没有gc。ResourceCanary改良了作法,在gc前,设定一个普通可回收的对象的弱引用,将这个引用做为哨兵,当有gc发生,则这个对象被回收,反之,则这个对象还存活。依靠这个对象判断虚拟机是否真正执行了gc。

二、leakCanary分析的是完整的hprof文件,而ResourceCanary是先dump完整的hprof文件,后裁剪。比leakCanary多了一步裁剪工做,减少了hprof文件大小

可是,不管是leakCanary仍是ResourceCanary都不适合线上直接使用,由于完整的hprof文件很大,可能高达几百兆,不适合线上执行。

五、字节跳动的Tailor

Tailor是一个内存快照裁剪压缩工具,经过hook技术在c层write时裁剪文件,不用存完整的hprof文件,避免dump下来完整的hprof文件,可线上使用

该库的具体实现可查看:https://github.com/bytedance/tailor

六、StrictMode

StriceMode是谷歌官方出的运行时检测工具,主要包含虚拟机策略和线程策略,虚拟机策略能够检测Activity泄漏、Sql对象泄漏,某个类实例个数是否超出等,检测结果能够从log中打印

StrictMode.setVmPolicy(
                new StrictMode.VmPolicy.Builder()
                        //检测Activity泄漏
                        .detectActivityLeaks()
                        //检测数据库对象泄漏
                        .detectLeakedSqlLiteObjects()
                        //检测某个类的实例个数
                        .setClassInstanceLimit(PluginManager.class, 1)
                        //检测结果在log中打印,也可选择若是泄漏就退出应用
                        .penaltyLog()
                        .build());
复制代码

3、内存抖动和内存泄漏

一、内存抖动

如何发现:使用memory profiler 观察,内存呈锯齿状,忽高忽低

常见缘由:通常是程序频繁建立对象引发。或者是内存泄露致使了内存不足,再申请频繁引起gc致使

危害:频繁gc,致使卡顿

解决方案:一般可利用Memory Profiler观察内存抖动,查看内存分配较多,实例较多的部分,找出堆栈排查

二、内存泄漏

定义: 内存中存在没有用的对象回收不了

危害: 致使可用内存逐渐减小,严重时可能致使内存溢出

解决: 经过memory profiler 初步排查,可用内存是否逐渐减小,若是存在这种现象则可能有内存泄露。

在as.中使用堆转储功能下载hprof文件,经过platform tools中的工具转换格式后用mat打开分析。

一般找activity,查看存在对象个数,超过一个的不正常,用mat查看它的gc roots引用,找到缘由处理

4、常见的内存泄漏缘由

一、集合类

集合类添加元素后,在使用完后未及时清理集合,致使集合中的元素没法释放

List<Object> objectList = new ArrayList<>();        
       for (int i = 0; i < 10; i++) {
            Object o = new Object();
            objectList.add(o);
            //o对象已经没用了,可是objectList还存在,o没法释放
            o = null;
        }
复制代码

二、静态变量

static Context mContext;
复制代码

若是Activity被静态变量引用,则Activity会没法释放,须要特别注意

三、单例

单例这种状况咱们特别容易忽略,须要养成习惯避免泄漏

public class SingleInstanceClass {
    
   private static SingleInstanceClass instance;
   private Context mContext;
   private SingleInstanceClass(Context context) {
       mContext = context;
   }
   
   public static SingleInstanceClass getInstance(Context context) {
       if (instance == null) {
           instance = new SingleInstanceClass(context);
       }
       return instance;
   }
}
复制代码

这种状况下,context若是传的是Activity就会泄漏,应该传Application的Context

四、非静态内部类/匿名类

非静态内部类/匿名类会持有外部类的引用,而静态内部类不会

这种状况常常发生在使用Handler的状况时,Handler被Message引用,而消息能够延时触发,好比延时6秒触发。而在6秒内Activity已经销毁了。这时Handler还不能释放,而Handler是非静态内部类/匿名类状况下,则会引用Activity,这时会形成泄漏

五、小结

本文介绍了三种常见的内存问题,6种内存分析和检测的工具,内存抖动、内存泄漏的常见处理方式,以及Android中多种常见的因为编码不规范形成内存泄漏的缘由。实际生产中,内存问题很是隐蔽难以发现,最好是配合线上线下处理,线下使用如leakCanary这种优秀工具检测,线上经过对重点模块内存,oom率,gc次数等进行回传,针对部分用户进行内存快照回传自动化分析监控等操做,来持续地以较低成本进行内存优化。

相关文章
相关标签/搜索