Android 内存泄露分析

2023-11-06

1 内存泄漏简介

内存泄漏是指内存空间使用完毕后无法被释放的现象。尽管Java有垃圾回收机制(GC),但是对于还保持着引用,逻辑上却已经不会再用到的对象,垃圾回收器不会回收它们。

内存泄漏带来的危害:

  • 用户对单次的内存泄漏并没有什么感知,但当可用的空闲空间越来越少,GC就会更容易被触发,GC进行时会停止其他线程的工作,因此有可能会造成界面卡顿等情况。
  • 后续需要分配内存的时候,很容易导致内存空间不足而出现 OOM(内存溢出)。

2 常见的内存泄漏场景

2.1 static 关键字修饰成员变量

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

  • static Activity
    static_activity
    这里也会提示有内存泄漏。
  • static View
    如果一个 View 初始化耗费大量资源,而且在一个 Activity 生命周期内保持不变,那可以把它变成 static,加载到视图树上(View Hierachy)。当 Activity 被销毁时,应当释放资源,否则就会导致内存泄漏。

解决方案:

  1. 尽量避免 static 成员变量引用资源耗费过多的实例(如 Context),若需引用 Context,则尽量使用Applicaiton的 Context。
  2. 使用弱引用(WeakReference) 代替强引用持有实例。

2.2 非静态内部类/ 匿名类

非静态内部类 / 匿名类默认持有外部类的引用,而静态内部类则不会。常见的情况有以下三种。

2.2.1 非静态内部类

如果非静态内部类所创建的实例是静态的,其生命周期等于应用的生命周期。非静态内部类默认持有外部类的引用而导致外部类无法释放,最终造成内存泄露。即外部类中持有非静态内部类的静态对象。

public class MainActivity extends AppCompatActivity {
    //非静态内部类的静态实例引用
    public static InnerClass innerClass = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //保证非静态内部类的实例只有1个
        if (innerClass == null) {
            innerClass = new InnerClass();
        }
    }

    // 非静态内部类
    private class InnerClass {
        //...
    }
}

当 MainActivity 销毁时,因非静态内部类单例的引用,innerClass 的生命周期等于应用的生命周期,持有外部类 MainActivity 的引用,故 MainActivity 无法被 GC 回收,从而导致内存泄漏。

解决方案:

  1. 将非静态内部类设置为:静态内部类(静态内部类默认不持有外部类的引用)
  2. 该内部类抽取出来封装成一个单例
  3. 尽量避免非静态内部类所创建的实例是静态的。

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

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

2.2.2.1 AsyncTask
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startAsyncTask();
    }

    private void startAsyncTask() {
        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... voids) {
                //执行耗时操作
                while(true);
            }
        }.execute();
    }
}
2.2.2.2 实现 Runnable 接口
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }

    class MyRunnable implements Runnable {
        @Override
        public void run() {
            //执行耗时操作
        }
    }
}
2.2.2.3 继承 Thread 类
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new MyThread().start();
    }

    private class MyThread extends Thread {
        @Override
        public void run() {
            //执行耗时操作
        }
    }
}

解决方案:

  1. 使用静态内部类的方式,静态内部类不默认持有外部类的引用。
    private static class MyThread extends Thread {
        @Override
        public void run() {
            //执行耗时操作
        }
    }
  1. 当外部类结束生命周期时,强制结束线程。使得工作线程实例的生命周期与外部类的生命周期同步。
    @Override
    protected void onDestroy() {
        super.onDestroy();
        myThread.interrupt();
    }

2.3 Handler

在 Handler 消息队列还有未处理的消息 / 正在处理消息时,消息队列中的 Message 持有 Handler 实例的引用。如果 Handler 是非静态内部类 / 匿名内部类(2种使用方式),就会默认持有外部类的引用(如 MainActivity 实例)。

handler_leak
上述的引用关系会一直保持,直到 Handler 消息队列中的所有消息被处理完毕。在 Handler 消息队列还有未处理的消息 / 正在处理消息时,此时若需销毁外部类 MainActivity,但由于上述引用关系,垃圾回收器(GC)无法回收 MainActivity,从而造成内存泄漏。

public class MainActivity extends AppCompatActivity {
    private MyHandler myHandler;

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

        myHandler = new MyHandler();
        new Thread() {
            @Override
            public void run() {
                try {
                    //执行耗时操作
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //发送消息
                myHandler.sendEmptyMessage(1);
            }
        }.start();
    }

    private class MyHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {
            //处理消息事件
        }
    }
}

解决方案:

  1. 使用静态内部类+弱引用的方式,保证外部类能被回收。因为弱引用的对象拥有短暂的生命周期,在垃圾回收器线程扫描时,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
public class MainActivity extends AppCompatActivity {
    private MyHandler myHandler;

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

        myHandler = new MyHandler(this);
        new Thread() {
            @Override
            public void run() {
                try {
                    //执行耗时操作
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //发送消息
                myHandler.sendEmptyMessage(1);
            }
        }.start();
    }

    public void test() {
        Log.d("MainActivity", "test");
    }

    private static class MyHandler extends Handler {
        //定义弱引用实例
        private WeakReference<Activity> reference;

        //在构造方法中传入需持有的Activity实例
        public MyHandler(Activity activity) {
            //使用 WeakReference 弱引用持有 Activity 实例
            reference = new WeakReference<Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            //处理消息事件
            //调用Activity实例中的方法
            ((MainActivity) reference.get()).test();
        }
    }
}
  1. 当外部类结束生命周期时,清空 Handler 内消息队列。

使用建议:
为了保证 Handler 中消息队列中的所有消息都能被执行,此处推荐使用解决方案1,即静态内部类+弱引用的方式。

2.4 资源对象使用后未关闭

对于资源的使用(如广播 BraodcastReceiver、文件流 File、数据库游标 Cursor、图片资源 Bitmap等),若在 Activity 销毁时无及时关闭 / 注销这些资源,则这些资源将不会被回收,从而造成内存泄漏。

解决方案:

//对于广播BroadcastReceiver:注销注册
unregisterReceiver(broadcastReceiver);

//对于文件流File:关闭流
inputStream / outputStream.close();

//对于数据库游标cursor:使用后关闭游标
cursor.close();

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

// 对于动画(属性动画),将动画设置成无限循环播放setRepeatCount(ValueAnimator.INFINITE);后
// 在Activity退出时记得停止动画
animator.cancel();

关闭以上对象的时候注意做非空判断。

2.5 WebView内存泄露

WebView 内部的一些线程持有 Activity 对象,使得 Activity 无法释放,从而导致内存泄漏。

解决方案:

    @Override
    protected void onDestroy() {
        if (mWebView != null) {
            ViewParent parent = mWebView.getParent();
            if (parent != null) {
                ((ViewGroup) parent).removeView(mWebView);
            }
            mWebView.stopLoading();
            mWebView.getSettings().setJavaScriptEnabled(false);
            mWebView.clearHistory();
            mWebView.clearView();
            mWebView.removeAllViews();
            mWebView.destroy();
            mWebView = null;
        }
        super.onDestroy();
    }

不建议在 xml 中创建 WebView,因为在 xml 中创建的 WebView 会持有 Activity 的 Context 对象。

2.6 单例模式造成的内存泄漏

由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。

public class Singleton {
    private static Singleton instance;
    private Context mContext;
    private Singleton(Context context){
        this.mContext = context;
    }

    public static Singleton getInstance(Context context){
        if (instance == null){
            synchronized (Singleton.class){
                if (instance == null){
                    instance = new Singleton(context);
                }
            }
        }
        return instance;
    }
}

这是一个单例模式,当创建这个单例的时候,由于需要传入一个 Context:

  1. 如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这没有问题。
  2. 如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 的内存并不会被回收,这就造成泄漏了。

解决方案:
将 new Singleton(context) 改为 new Singleton(context.getApplicationContext()) 即可,这样便和传入的 Activity 没关系了。

public class Singleton {
    private static Singleton instance;
    private Context mContext;
    private Singleton(Context context){
        this.mContext = context;
    }

    public static Singleton getInstance(Context context){
        if (instance == null){
            synchronized (Singleton.class){
                if (instance == null){
                    instance = new Singleton(context.getApplicationContext());// 使用Application 的context
                }
            }
        }
        return instance;
    }
}

3 内存泄漏分析工具

3.1 lint

lint 是一个静态代码分析工具,同样也可以用来检测部分会出现内存泄露的代码,平时编程注意 lint 提示的各种黄色警告即可。如:
lint
也可以手动检测,在 Android Studio 中选择 Analyze->Inspect Code。

Analyze
然后会弹出弹窗选择检测范围。

inspect

点击 OK 等待分析结果:

inspect_result
这个工具除了会检测内存泄漏,还会检测代码是否规范、是否有没用到的导包、可能的bug、安全问题等等。

3.2 Memory Profile

Memory Profile 的使用

3.3 LeakCanary

LeakCanary 的使用

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android 内存泄露分析 的相关文章

随机推荐

  • windows:NGINX配置nginx.conf不生效的解决方法

    问题 如标题 参考 Windows系统下NGINX配置nginx conf不生效的解决方法 51CTO博客 nginx 配置不生效 解决办法 运行命令 taskkill IM nginx exe F 来关闭这些进程 之后重新启动nginx
  • Flask项目(四)房屋列表页、订单、对接支付宝、数据库优化

    Flask项目 房屋列表页 订单 对接支付宝 数据库优化 房屋列表页 from import api from flask import g current app jsonify request session from ihome ut
  • 【数据库】表的内外连接(重点)

    文章目录 表的内连接和外连接 1 内连接 2 外连接 2 1 左外连接 2 2 右外连接 表的内连接和外连接 表的连接分为内连和外连 1 内连接 内连接实际上就是利用where子句对两种表形成的笛卡尔积进行筛选 前面几个博客的查询都是内链接
  • C++11多线程---互斥量mutex、锁lock、条件变量std::condition_variable

    目录 一 lock guard lock 1 直接操作mutex的lock unlock函数 2 使用 lock guard unique lock 自动加锁 解锁 类似智能指针 3 unique lock和lock guard区别 二 条
  • postman——基础操作——API授权

    当发送请求时 通常会包括参数 用以确保请求能够访问和范湖所需的数据 postman提供了授权类型 能在postman中处理身份验证问题 参考网址 https learning getpostman com docs postman send
  • 上传文件sftp和base 64上传的优缺点?

    上传文件时 SFTP和Base64编码都是常见的方式 它们具有各自的优缺点 SFTP SSH File Transfer Protocol 上传的优缺点 优点 安全性高 SFTP通过SSH协议进行传输 数据在传输过程中会进行加密 提供了较高
  • APP性能优化系列:内存优化-内存泄露详解

    转自http blog csdn net a910626 article details 50849760 一 内存泄露概念 在java中 如果一个对象没有可用价值了 但又被其他引用所指向 那么这个对象对于gc来说就不是一个垃圾 所以不会对
  • exec函数族

    前言 exec函数族提供了一个在进程中启动另一个程序执行的方法 它可以根据指定的文件名或目录名找到可执行文件 并用它来取代原调用进程的数据段 代码段和堆栈段 在执行完之后 原调用进程的内容除了进程号外 其他全部被新的进程替换了 另外 这里的
  • zabbix自动发现和自动注册

    1 配置zabbix自动发现 1 gt 自动发现主要是希望通过发现网络中的主机 并自动把主机添加到监控中 并关联特定的模板 实现自动监控 例如在办公网络中 希望通过ZabbixAgent监控所有工作电脑 只需要把新安装的电脑开放防火墙100
  • DNS服务器的搭建

    DNS 域名系统 它用于TCP IP网路 它提供的服务是用来将主机名和域名转换为IP地址的工作 DNS常用术语 1 DNS服务器 提供域名解析服务的主机 2 DNS客户机 任何联网的需要查询主机域名信息的主机 3 正向解析 将域名称解析为I
  • JAVA多线程服务器,UI实现

    JAVA多线程服务器 UI实现 服务器选择端口启动服务 客户端填入服务器名和端口进行服务器连接 实现功能 多客户端的同时连接 客户端上线通知 点击send按钮或者敲击回车进行发送 消息发送时间显示 客户端与服务器实时通信 可同时发送多条消息
  • 运算放大器设计及应用参讨(三)

    1 请问运放上的 15V 电压不稳定是不是也影响输出波形 问 请问运放上的 15V 电压不稳定是不是也影响输出波形 答 1 电源电压的波动会影响到输出的 但是对于输出能影响到多少 运放中有个参数PSRR 可以体现出来 例如PSRR 80dB
  • 华硕z97不识别m2固态_固态盘M.2、SATA有这样的差别,难怪大家都选“它”

    M 2接口SSD 现在常见的固态硬盘都是M 2或者SATA3接口 那么他们之间的区别是什么呢 今天 小编就以问答的形式 给大家普及下M 2固态硬盘的相关知识 1 什么是M 2接口 M 2是硬盘的一种接口 主要优势就是比传统的SATA3 0接
  • 对随机变量的简单理解

    首先看下官方定义 随机变量是从样本空间投影到实数轴的一个广义的实值函数 对任意一个样本点w 存在唯一的实数X w 与之对应 我画了下图来解释这个定义 当我们需要研究事件发生的概率时 引入随机变量后 对事件概率的研究不再是重点 而是转化为对随
  • 变量名中的反义词

    add remove begin end create destroy insert delete first last get release increment decrement put get up down lock unlock
  • MySql.Data连接数据库mysql

    using MySql Data MySqlClient using MySql Data using System Data using System IO MySqlConnection con new MySqlConnection
  • SVN版本库的分支和Tag

    SVN的branch和tag管理都是通过copy实现的 它并没有独立的branche和tag概念 它仅仅是管理文件的历史 trunk branch tag对于SVN而言都是普通的文件夹 创建分支的svn命令 svn copy http sv
  • python中的linearregression_Python 线性回归(Linear Regression) - 到底什么是 regression?...

    背景 这个 linear Regression 中的 Regression 是什么意思 字面上 Regression 是衰退的意思 线性衰退 相信理解了这个词 对线性回归可能印象深刻些 Regression 到底是什么意思 搜了一番 原来是
  • mongodb的时间差 8小时

    存储在mongodb中的时间是标准时间UTC 0 00 而中国的时区是 8 00 所以实际的显示时间是加了八小时的 取出时间时 需要减去八小时
  • Android 内存泄露分析

    1 内存泄漏简介 内存泄漏是指内存空间使用完毕后无法被释放的现象 尽管Java有垃圾回收机制 GC 但是对于还保持着引用 逻辑上却已经不会再用到的对象 垃圾回收器不会回收它们 内存泄漏带来的危害 用户对单次的内存泄漏并没有什么感知 但当可用