写点什么

Android 性能优化:手把手带你全面了解内存泄露

用户头像
Android架构
关注
发布于: 2 小时前

public class Sample {


// 该类的实例对象的成员变量 s1、mSample1 & 指向对象存放在堆内存中 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();

4.2 内存释放策略

此处主要讲解对象分配(即堆式分配)的内存释放策略 = Java垃圾回收器(GC


由于静态分配不需释放、栈式分配仅 通过帧栈自动出、入栈,较简单,故不详细描述


  • Java垃圾回收器(GC)的内存释放 = 垃圾回收算法,主要包括:



  • 具体介绍如下




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

5.1 集合类

  • 内存泄露原因 集合类 添加元素后,仍引用着 集合元素对象,导致该集合元素对象不可被回收,从而 导致内存泄漏

  • 实例演示


// 通过 循环申请 Object 对象 & 将申请的对象逐个放入到集合 ListList<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


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


个集合中有许多元素,故最简单的方法 = 清空集合对象 & 设置为null


// 释放 objectListobjectList.clear();objectList=null;

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

  • 储备知识 被 Static 关键字修饰的成员变量的生命周期 = 应用程序的生命周期

  • 泄露原因 若使被 Static 关键字修饰的成员变量 引用耗费资源过多的实例(如Context),则容易出现该成员变量的生命周期 > 引用实例生命周期的情况,当引用实例需结束生命周期销毁时,会因静态变量的持有而无法被回收,从而出现内存泄露

  • 实例讲解


public class ClassName {// 定义 1 个静态变量 private static Context mContext;//...// 引用的是 Activity 的 contextmContext = context;


// 当 Activity 需销毁时,由于 mContext = 静态 & 生命周期 = 应用程序的生命周期,故 Activity 无法被回收,从而出现内存泄露


}


  • 解决方案


  1. 尽量避免 Static 成员变量引用资源耗费过多的实例(如 Context


若需引用 Context,则尽量使用ApplicaitonContext


  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;}}


  • 解决方案 单例模式引用的对象的生命周期 = 应用的生命周期


如上述实例,应传递ApplicationContext,因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;


@Overrideprotected 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,建议使用 ApplicationContext

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

  • 储备知识 多线程的使用方法 = 非静态内部类 / 匿名类;即 线程类 属于 非静态内部类 / 匿名类

  • 泄露原因 当 工作线程正在处理任务 & 外部类需销毁时, 由于 工作线程实例 持有外部类引用,将使得外部类无法被垃圾回收器(GC)回收,从而造成 内存泄露


  1. 多线程主要使用的是:AsyncTask、实现Runnable接口 & 继承Thread

  2. 前 3 者内存泄露的原理相同,此处主要以继承Thread类 为例说明


  • 实例演示


/**


  • 方式 1:新建 Thread 子类(内部类)*/

  • public class MainActivity extends AppCompatActivity {


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


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


}// 自定义的 Thread 子类 private class MyThread extends Thread{@Overridepublic 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:";


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


// 通过匿名内部类 实现多线程 new Thread() {@Overridepublic 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:";@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);


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


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


/**


  • 解决方案 2:当外部类结束生命周期时,强制结束线程

  • 原理:使得 工作线程实例的生命周期 与 外部类的生命周期 同步

  • 具体实现:当 外部类(此处以 Activity 为例) 结束生命周期时(此时系统会调用 onDestroy()),强制结束线程(调用 stop())*/@Overrideprotected void onDestroy() {super.onDestroy();Thread.stop();// 外部类 Activity 生命周期结束时,强制结束线程}

5.3.3 消息传递机制:Handler

具体请看文章:Android 内存泄露:详解 Handler 内存泄露的原因

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android 性能优化:手把手带你全面了解内存泄露