关于android:Android性能优化之内存泄漏你想要的这里都有

1次阅读

共计 7978 个字符,预计需要花费 20 分钟才能阅读完成。

前言

在 Android 中,内存泄露的景象非常常见;而内存泄露导致的结果会使得利用 Crash
本文 全面介绍了内存泄露的实质、起因 & 解决方案,最终提供一些常见的内存泄露剖析工具,心愿你们会喜爱。

目录

1.png

1. 简介

即 ML(Memory Leak)
指 程序在申请内存后,当该内存不需再应用 但 却无奈被开释 & 归还给 程序的景象

2. 对应用程序的影响

容易使得应用程序产生内存溢出,即 OOM
内存溢出 简介:

1.png

3. 产生内存泄露的实质起因

具体形容

1.jpg

  • 特地留神
    从机制上的角度来说,因为 Java 存在垃圾回收机制(GC),理当不存在内存泄露;呈现内存泄露的起因仅仅是内部人为起因 = 有意识地持有对象援用,使得 持有援用者的生命周期 > 被援用者的生命周期

4. 储备常识:Android 内存管理机制

4.1 简介

1.png

上面,将针对回收 过程、对象、变量的内存调配 & 回收进行具体解说

4.2 针对过程的内存策略

a. 内存调配策略

由 ActivityManagerService 集中管理 所有过程的内存调配

b. 内存回收策略

步骤 1:Application Framework 决定回收的过程类型
Android 中的过程 是托管的;当过程空间缓和时,会 按过程优先级低 ->> 高的程序 主动回收过程
Android 将过程分为 5 个优先等级,具体如下:

1.png

  • 步骤 2:Linux 内核真正回收具体过程
    ActivityManagerService 对 所有过程进行评分(评分寄存在变量 adj 中)
    更新评分到 Linux 内核
    由 Linux 内核实现真正的内存回收

此处仅总结流程,这其中的过程简单,有趣味的读者可钻研零碎源码 ActivityManagerService.java

4.2 针对对象、变量的内存策略

  • Android 的对于对象、变量的内存策略同 Java
  • 内存治理 = 对象 / 变量的内存调配 + 内存开释

上面,将具体解说内存调配 & 内存开释策略

a. 内存调配策略
  • 对象 / 变量的内存调配 由程序主动 负责
  • 共有 3 种:动态调配、栈式调配、& 堆式调配,别离面向动态变量、局部变量 & 对象实例
    具体介绍如下
  

![](https://upload-images.jianshu.io/upload_images/5258053-9c0b664ad81e4345.png)

1.png

  

注:用 1 个实例解说 内存调配

public class Sample {    
    int s1 = 0;
    Sample mSample1 = new Sample();   
    
    // 办法中的局部变量 s2、mSample2 寄存在 栈内存
    // 变量 mSample2 所指向的对象实例寄存在 堆内存
      // 该实例的成员变量 s1、mSample1 也寄存在栈中
    public void method() {        
        int s2 = 0;
        Sample mSample2 = new Sample();}
}
    // 变量 mSample3 所指向的对象实例寄存在堆内存
    // 该实例的成员变量 s1、mSample1 也寄存在堆内存中
    Sample mSample3 = new Sample();

b. 内存开释策略
  • 对象 / 变量的内存开释 由 Java 垃圾回收器(GC)/ 帧栈 负责
  • 此处次要解说对象调配(即堆式调配)的内存开释策略 = Java 垃圾回收器(GC)

因为动态调配不需开释、栈式调配仅 通过帧栈主动出、入栈,较简略,故不详细描述

  • Java 垃圾回收器(GC)的内存开释 = 垃圾回收算法,次要包含:
  

![](https://upload-images.jianshu.io/upload_images/5258053-2695060b1627b587.png)

1.png
  • 具体介绍如下
  

![](https://upload-images.jianshu.io/upload_images/5258053-c53cce28814ab12e.png)

1.png

5. 常见的内存泄露起因 & 解决方案

常见引发内存泄露起因次要有:

  1. 汇合类
  2. Static 关键字润饰的成员变量
  3. 非动态外部类 / 匿名类
  4. 资源对象应用后未敞开
    上面,我将具体介绍每个引发内存泄露的起因

5.1 汇合类

  • 内存泄露起因
    汇合类 增加元素后,仍援用着 汇合元素对象,导致该汇合元素对象不可被回收,从而 导致内存透露
    实例演示:
// 通过 循环申请 Object 对象 & 将申请的对象一一放入到汇合 List
List<Object> objectList = new ArrayList<>();        
       for (int i = 0; i < 10; i++) {Object o = new Object();
            objectList.add(o);
            o = null;
        }
// 虽开释了汇合元素援用的自身:o=null)// 但汇合 List 依然援用该对象,故垃圾回收器 GC 仍然不可回收该对象
  • 解决方案
    汇合类 增加汇合元素对象 后,在应用后必须从汇合中删除

因为 1 个汇合中有许多元素,故最简略的办法 = 清空集合对象 & 设置为 null

 // 开释 objectList
        objectList.clear();
        objectList=null;

5.2 Static 关键字润饰的成员变量

  • 储备常识
    被 Static 关键字润饰的成员变量的生命周期 = 应用程序的生命周期
  • 泄露起因
    若使被 Static 关键字润饰的成员变量 援用消耗资源过多的实例(如 Context),则容易呈现该成员变量的生命周期 > 援用实例生命周期的状况,当援用实例需完结生命周期销毁时,会因动态变量的持有而无奈被回收,从而呈现内存泄露
    实例解说:
public class ClassName {
 // 定义 1 个动态变量
 private static Context mContext;
 //...
// 援用的是 Activity 的 context
 mContext = context; 

// 当 Activity 需销毁时,因为 mContext = 动态 & 生命周期 = 应用程序的生命周期,故 Activity 无奈被回收,从而呈现内存泄露

}
  • 解决方案
  1. 尽量避免 Static 成员变量援用资源消耗过多的实例(如 Context)

若需援用 Context,则尽量应用 Applicaiton 的 Context

  1. 应用 弱援用(WeakReference)代替 强援用 持有实例

注:动态成员变量有个十分典型的例子 = 单例模式

  • 储备常识
    单例模式 因为其动态个性,其生命周期的长度 = 应用程序的生命周期
  • 泄露起因
    若 1 个对象已不需再应用 而单例对象还持有该对象的援用,那么该对象将不能被失常回收 从而 导致内存透露

实例演示:

// 创立单例时,需传入一个 Context
// 若传入的是 Activity 的 Context,此时单例 则持有该 Activity 的援用
// 因为单例始终持有该 Activity 的援用(直到整个利用生命周期完结),即便该 Activity 退出,该 Activity 的内存也不会被回收
// 特地是一些宏大的 Activity,此处非常容易导致 OOM

public class SingleInstanceClass {    
    private static SingleInstanceClass instance;    
    private Context mContext;    
    private SingleInstanceClass(Context context) {this.mContext = context; // 传递的是 Activity 的 context}  
  
    public SingleInstanceClass getInstance(Context context) {if (instance == null) {instance = new SingleInstanceClass(context);
        }        
        return instance;
    }
}
  • 解决方案
    单例模式援用的对象的生命周期 = 利用的生命周期

如上述实例,应传递 Application 的 Context,因 Application 的生命周期 = 整个利用的生命周期

public class SingleInstanceClass {    
    private static SingleInstanceClass instance;    
    private Context mContext;    
    private SingleInstanceClass(Context context) {this.mContext = context.getApplicationContext(); // 传递的是 Application 的 context
    }    

    public SingleInstanceClass getInstance(Context context) {if (instance == null) {instance = new SingleInstanceClass(context);
        }        
        return instance;
    }
}

5.3 非动态外部类 / 匿名类

  • 储备常识
    非动态外部类 / 匿名类 默认持有 外部类的援用;而动态外部类则不会
  • 常见状况
    3 种,别离是:非动态外部类的实例 = 动态、多线程、消息传递机制(Handler)

5.3.1 非动态外部类的实例 = 动态

  • 泄露起因
    若 非动态外部类所创立的实例 = 动态(其生命周期 = 利用的生命周期),会因 非动态外部类默认持有外部类的援用 而导致外部类无奈开释,最终 造成内存泄露

即 外部类中 持有 非动态外部类的动态对象
实例演示:

// 背景:a. 在启动频繁的 Activity 中,为了防止反复创立雷同的数据资源,会在 Activity 外部创立一个非动态外部类的单例
   b. 每次启动 Activity 时都会应用该单例的数据

public class TestActivity extends AppCompatActivity {  
    
    // 非动态外部类的实例的援用
    // 注:设置为动态  
    public static InnerClass innerClass = null; 
   
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);   

        // 保障非动态外部类的实例只有 1 个
        if (innerClass == null)
            innerClass = new InnerClass();}

    // 非动态外部类的定义    
    private class InnerClass {//...}
}

// 造成内存泄露的起因:// a. 当 TestActivity 销毁时,因非动态外部类单例的援用(innerClass)的生命周期 = 利用 App 的生命周期、持有外部类 TestActivity 的援用
    // b. 故 TestActivity 无奈被 GC 回收,从而导致内存透露
  • 解决方案
  1. 将非动态外部类设置为:动态外部类(动态外部类默认不持有外部类的援用)
  2. 该外部类抽取进去封装成一个单例
  3. 尽量 防止 非动态外部类所创立的实例 = 动态

若需应用 Context,倡议应用 Application 的 Context

5.3.2 多线程:AsyncTask、实现 Runnable 接口、继承 Thread 类

  • 储备常识
    多线程的应用办法 = 非动态外部类 / 匿名类;即 线程类 属于 非动态外部类 / 匿名类
  • 泄露起因
    当 工作线程正在解决工作 & 外部类需销毁时,因为 工作线程实例 持有外部类援用,将使得外部类无奈被垃圾回收器(GC)回收,从而造成 内存泄露

多线程次要应用的是:AsyncTask、实现 Runnable 接口 & 继承 Thread 类
前 3 者内存泄露的原理雷同,此处次要以继承 Thread 类 为例阐明

实例演示

/** 
     * 形式 1:新建 Thread 子类(外部类)*/  
        public class MainActivity extends AppCompatActivity {

        public static final String TAG = "carson:";
        @Override
        public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            // 通过创立的外部类 实现多线程
            new MyThread().start();

        }
        // 自定义的 Thread 子类
        private class MyThread extends Thread{
            @Override
            public void run() {
                try {Thread.sleep(5000);
                    Log.d(TAG, "执行了多线程");
                } catch (InterruptedException e) {e.printStackTrace();
                }
            }
        }
    }

   /** 
     * 形式 2:匿名 Thread 外部类
     */ 
     public class MainActivity extends AppCompatActivity {

    public static final String TAG = "carson:";

    @Override
    public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 通过匿名外部类 实现多线程
        new Thread() {
            @Override
            public void run() {
                try {Thread.sleep(5000);
                    Log.d(TAG, "执行了多线程");
                } catch (InterruptedException e) {e.printStackTrace();
                }

            }
        }.start();}
}


/** 
  * 剖析:内存泄露起因
  */ 
  // 工作线程 Thread 类属于非动态外部类 / 匿名外部类,运行时默认持有外部类的援用
  // 当工作线程运行时,若外部类 MainActivity 需销毁
  // 因为此时工作线程类实例持有外部类的援用,将使得外部类无奈被垃圾回收器(GC)回收,从而造成 内存泄露

  • 解决方案
    从下面可看出,造成内存泄露的起因有 2 个要害条件:
  1. 存在”工作线程实例 持有外部类援用“的援用关系
  2. 工作线程实例的生命周期 > 外部类的生命周期,即工作线程仍在运行 而 外部类需销毁
    解决方案的思路 = 使得上述任 1 条件不成立 即可。
// 共有 2 个解决方案:动态外部类 & 当外部类完结生命周期时,强制完结线程
// 具体形容如下

   /** 
     * 解决形式 1:动态外部类
     * 原理:动态外部类 不默认持有外部类的援用,从而使得“工作线程实例 持有 外部类援用”的援用关系 不复存在
     * 具体实现:将 Thread 的子类设置成 动态外部类
     */  
        public class MainActivity extends AppCompatActivity {

        public static final String TAG = "carson:";
        @Override
        public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            // 通过创立的外部类 实现多线程
            new MyThread().start();

        }
        // 剖析 1:自定义 Thread 子类
        // 设置为:动态外部类
        private static class MyThread extends Thread{
            @Override
            public void run() {
                try {Thread.sleep(5000);
                    Log.d(TAG, "执行了多线程");
                } catch (InterruptedException e) {e.printStackTrace();
                }
            }
        }
    }

   /** 
     * 解决方案 2:当外部类完结生命周期时,强制完结线程
     * 原理:使得 工作线程实例的生命周期 与 外部类的生命周期 同步
     * 具体实现:当 外部类(此处以 Activity 为例)完结生命周期时(此时零碎会调用 onDestroy()),强制完结线程(调用 stop())*/ 
     @Override
    protected void onDestroy() {super.onDestroy();
        Thread.stop();
        // 外部类 Activity 生命周期完结时,强制完结线程
    }

5.3.3 消息传递机制:Handler

Android 内存泄露:详解 Handler 内存泄露的起因与解决方案

5.4 资源对象应用后未敞开

  • 泄露起因
    对于资源的应用(如 播送 BraodcastReceiver、文件流 File、数据库游标 Cursor、图片资源 Bitmap 等),若在 Activity 销毁时无及时敞开 / 登记这些资源,则这些资源将不会被回收,从而造成内存透露
  • 解决方案
    在 Activity 销毁时 及时敞开 / 登记资源
// 对于 播送 BraodcastReceiver:登记注册
unregisterReceiver()

// 对于 文件流 File:敞开流
InputStream / OutputStream.close()

// 对于数据库游标 cursor:应用后敞开游标
cursor.close()// 对于 图片资源 Bitmap:Android 调配给图片的内存只有 8M,若 1 个 Bitmap 对象占内存较多,当它不再被应用时,应调用 recycle()回收此对象的像素所占用的内存;最初再赋为 null 
Bitmap.recycle();Bitmap = null;

// 对于动画(属性动画)// 将动画设置成有限循环播放 repeatCount =“infinite”后
// 在 Activity 退出时记得进行动画

5.5 其余应用

  • 除了上述 4 种常见状况,还有一些日常的应用会导致内存泄露
  • 次要包含:Context、WebView、Adapter,具体介绍如下
  

![](https://upload-images.jianshu.io/upload_images/5258053-052f56003d938b7c.png)

1.png

5.6 总结

上面,我将用一张图总结 Android 中内存泄露的起因 & 解决方案

1.png

6. 辅助剖析内存泄露的工具

  • 哪怕齐全理解 内存泄露的起因,但不免还是会呈现内存泄露的景象
  • 上面将简略介绍几个支流的剖析内存泄露的工具,别离是
  1. MAT(Memory Analysis Tools)
  2. Heap Viewer
  3. Allocation Tracker
  4. Android Studio 的 Memory Monitor
  5. LeakCanary

6.1 MAT(Memory Analysis Tools)

  • 定义:一个 EclipseJava Heap 内存剖析工具 ->> 下载地址
  • 作用:查看以后内存占用状况

通过剖析 Java 过程的内存快照 HPROF 剖析,疾速计算出在内存中对象占用的大小,查看哪些对象不能被垃圾收集器回收 & 可通过视图直观地查看可能造成这种后果的对象

  • 具体应用:MAT 应用攻略

6.2 Heap Viewer

  • 定义:一个的 Java Heap 内存剖析工具
  • 作用:查看以后内存快照

可查看 别离有哪些类型的数据在堆内存总 & 各种类型数据的占比状况

  • 具体应用:Heap Viewer 应用攻略

6.3 Allocation Tracker

  • 简介:一个内存追踪剖析工具
  • 作用:追踪内存调配信息,按顺序排列
  • 具体应用:Allocation Tracker 应用攻略

6.4 Memory Monitor

  • 简介:一个 Android Studio 自带 的图形化检测内存工具
  • 作用:跟踪零碎 / 利用的内存应用状况。外围性能如下
  

![](https://upload-images.jianshu.io/upload_images/5258053-9449e4d758db0c95.png)

1.png
  • 具体应用:Android Studio 的 Memory Monitor 应用攻略

6.5 LeakCanary

  • 简介:一个 square 出品的 Android 开源库 ->> 下载地址
  • 作用:检测内存泄露
  • 具体应用:https://www.liaohuqiu.net/cn/posts/leak-canary/

7. 总结

本文 全面介绍了内存泄露的实质、起因 & 解决方案,心愿大家在开发时尽量避免呈现内存泄露

我的库存,须要的小伙伴请点击我的 GitHub 收费支付

正文完
 0