常见的八种导致 APP 内存泄漏的问题

2023-11-18


像 Java 这样具有垃圾回收功能的语言的好处之一,就是程序员无需手动管理内存分配。这减少了段错误(segmentation fault)导致的闪退,也减少了内存泄漏导致的堆空间膨胀,让编写的代码更加安全。然而,Java 中依然有可能发生内存泄漏。所以你的安卓 APP 依然有可能浪费了大量的内存,甚至由于内存耗尽(OOM)导致闪退。


传统的内存泄漏是由忘记释放分配的内存导致的,而逻辑上的内存泄漏则是由于忘记在对象不再被使用的时候释放对其的引用导致的。如果一个对象仍然存在强引用,垃圾回收器就无法对其进行垃圾回收。在安卓平台,泄漏

Context

对象问题尤其严重。这是因为像

Activity

这样的 Context 对象会引用大量很占用内存的对象,例如 View 层级,以及其他的资源。如果 Context 对象发生了内存泄漏,那它引用的所有对象都被泄漏了。安卓设备大多内存有限,如果发生了大量这样的内存泄漏,那内存将很快耗尽。


如果一个对象的

合理生命周期

没有清晰的定义,那判断逻辑上的内存泄漏将是一个见仁见智的问题。幸运的是,activity 有清晰的生命周期定义,使得我们可以很明确地判断 activity 对象是否被内存泄漏。

onDestroy()

函数将在 activity 被销毁时调用,无论是程序员主动销毁 activity,还是系统为了回收内存而将其销毁。如果 onDestroy 执行完毕之后,activity 对象仍被 heap root 强引用,那垃圾回收器就无法将其回收。所以我们可以把生命周期结束之后仍被引用的 activity 定义为被泄漏的 activity。


Activity 是非常重量级的对象,所以我们应该极力避免妨碍系统对其进行回收。然而有多种方式会让我们无意间就泄露了 activity 对象。我们把可能导致 activity 泄漏的情况分为两类,一类是使用了进程全局(process-global)的静态变量,无论 APP 处于什么状态,都会一直存在,它们持有了对 activity 的强引用进而导致内存泄漏,另一类是生命周期长于 activity 的线程,它们忘记释放对 activity 的强引用进而导致内存泄漏。下面我们就来详细分析一下这些可能导致 activity 泄漏的情况。


1. 静态 Activity


泄漏 activity 最简单的方法就是在 activity 类中定义一个 static 变量,并且将其指向一个运行中的

activity 实例

。如果在 activity 的生命周期结束之前,没有清除这个引用,那它就会泄漏了。这是因为 activity(例如 MainActivity) 的类对象是静态的,一旦加载,就会在 APP 运行时一直常驻内存,因此如果类对象不卸载,其静态成员就不会被垃圾回收。

void setStaticActivity() {

  activity = this;

}

 

View saButton = findViewById(R.id.sa_button);

saButton.setOnClickListener(new View.OnClickListener() {

  @Override public void onClick(View v) {

    setStaticActivity();

    nextActivity();

  }

});


内存泄漏场景 1 - Static Activity


2. 静态 View


另一种类似的情况是对经常启动的 activity 实现一个单例模式,让其常驻内存可以使它能够快速恢复状态。然而,就像前文所述,不遵循系统定义的 activity 生命周期是非常危险的,也是没必要的,所以我们应该极力避免。


但是如果我们有一个创建起来非常耗时的 View,在同一个 activity 不同的生命周期中都保持不变呢?所以让我们为它实现一个单例模式,就像这段代码。现在一旦 activity 被销毁,那我们就应该释放大部分的内存了。

void setStaticView() {

  view = findViewById(R.id.sv_button);

}

 

View svButton = findViewById(R.id.sv_button);

svButton.setOnClickListener(new View.OnClickListener() {

  @Override public void onClick(View v) {

    setStaticView();

    nextActivity();

  }

});



内存泄漏场景 2 - Static View


内存泄漏了!因为一旦 view 被加入到界面中,它就会持有 context 的强引用,也就是我们的 activity。由于我们通过一个静态成员引用了这个 view,所以我们也就引用了 activity,因此 activity 就发生了泄漏。所以一定不要把加载的 view 赋值给静态变量,如果你真的需要,那一定要确保在 activity 销毁之前将其

从 view 层级中移除


3. 内部类


现在让我们在 activity 内部定义一个类,也就是内部类。这样做的原因有很多,比如增加封装性和可读性。如果我们创建了一个内部类的对象,并且通过静态变量持有了 activity 的引用,那也会发生 activity 泄漏。

[代码]java代码:

void createInnerClass() {

    class InnerClass {

    }

    inner = new InnerClass();

}

 

View icButton = findViewById(R.id.ic_button);

icButton.setOnClickListener(new View.OnClickListener() {

    @Override public void onClick(View v) {

        createInnerClass();

        nextActivity();

    }

});



内存泄漏场景 3 - Inner Class

不幸的是,内部类能够引用外部类的成员这一优势,就是通过持有外部类的引用来实现的,而这正是 activity 泄漏的原因。


4. 匿名类


类似的,匿名类同样会持有定义它们的对象的引用。因此如果在

activity 内定义了一个匿名的 AsyncTask 对象

,就有可能发生内存泄漏了。如果 activity 被销毁之后 AsyncTask 仍然在执行,那就会组织垃圾回收器回收 activity 对象,进而导致内存泄漏,直到执行结束才能回收 activity。


[代码]java代码:

void startAsyncTask() {
    new AsyncTask<void, void,="" void="">() {
        @Override protected Void doInBackground(Void... params) {
            while(true);
        }
    }.execute();
}
 
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View aicButton = findViewById(R.id.at_button);
aicButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        startAsyncTask();
        nextActivity();
    }
});


 
     

内存泄漏场景 4 - AsyncTask

5. Handlers

同样的,定义一个匿名的 Runnable 对象并将其提交到 Handler 上也可能导致 activity 泄漏。Runnable 对象间接地引用了定义它的 activity 对象,而它会被提交到 Handler 的 MessageQueue 中,如果它在 activity 销毁时还没有被处理,那就会导致 activity 泄漏了。

void createHandler() {

    new Handler() {

        @Override public void handleMessage(Message message) {

            super.handleMessage(message);

        }

    }.postDelayed(new Runnable() {

        @Override public void run() {

            while(true);

        }

    }, Long.MAX_VALUE >> 1);

}

 

 

View hButton = findViewById(R.id.h_button);

hButton.setOnClickListener(new View.OnClickListener() {

    @Override public void onClick(View v) {

        createHandler();

        nextActivity();

    }

});

内存泄漏场景 5 - Handler

6. Threads

同样的,使用

ThreadTimerTask

也可能导致 activity 泄漏。

void spawnThread() {

    new Thread() {

        @Override public void run() {

            while(true);

        }

    }.start();

}

 

View tButton = findViewById(R.id.t_button);

tButton.setOnClickListener(new View.OnClickListener() {

  @Override public void onClick(View v) {

      spawnThread();

      nextActivity();

  }

});

内存泄漏场景 6 - Thread

7. Timer Tasks

只要它们是通过匿名类创建的,尽管它们在单独的线程被执行,它们也会持有对 activity 的强引用,进而导致内存泄漏。

 

void scheduleTimer() {
    new Timer().schedule(new TimerTask() {
        @Override
        public void run() {
            while(true);
        }
    }, Long.MAX_VALUE >> 1);
}
 
View ttButton = findViewById(R.id.tt_button);
ttButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        scheduleTimer();
        nextActivity();
    }
});
 

内存泄漏场景 7 - TimerTask

8. Sensor Manager


最后,系统服务可以通过 context.getSystemService 获取,它们负责执行某些后台任务,或者为硬件访问提供接口。如果 context 对象想要在服务内部的事件发生时被通知,那就需要把自己注册到服务的监听器中。然而,这会让服务持有 activity 的引用,如果程序员忘记在 activity 销毁时取消注册,那就会导致 activity 泄漏了。

void registerListener() {

       SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);

       Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);

       sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);

}

 

View smButton = findViewById(R.id.sm_button);

smButton.setOnClickListener(new View.OnClickListener() {

    @Override public void onClick(View v) {

        registerListener();

        nextActivity();

    }

});


内存泄漏场景 8 - Sensor Manager


现在,我们展示了八种很容易不经意间就泄漏大量内存的情景。请记住,最坏的情况下,你的 APP 可能会由于大量的内存泄漏而内存耗尽,进而闪退,但它并不总是这样。相反,内存泄漏会消耗大量的内存,但却不至于内存耗尽,这时,APP 会由于内存不够分配而频繁进行垃圾回收。垃圾回收是非常耗时的操作,会导致严重的卡顿。在 activity 内部创建对象时,一定要格外小心,并且要经常测试是否存在内存泄漏。


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

常见的八种导致 APP 内存泄漏的问题 的相关文章

  • 虚拟机占用磁盘越来越大的解决方案大总结

    原文链接 https blog csdn net Shine Su article details 119676646 作者 填坑小霸王 在电脑上安装Vmware 安装ubuntu16 04 进行开发 使用过程中没有在意 忽然有一天发现磁盘
  • CentOS 安装 docker详解

    前言 docker的简介我就不多说 本文主要讲解的是在CentOS 7 版本下怎样安装docker 最新默认稳定版本 查看Linux版本 执行如下命令查看 cat etc redhat release 可以看到是CentOS7 9版本的 开
  • pip安装更换镜像

    原文链接 使用pip来安装python包有时候安装起来会非常慢 因此需要换成国内的源来加速下载 1 使用命令 以Torch为例 pip install i https pypi tuna tsinghua edu cn simple tor
  • Linux系统与管理 - (五)应用安装与管理❤

    自说 学习路径 RPM软件包 YUM安装 RPM与YUM的优劣 自说 在使用Linux系统的过程中我们经常需要安装和更新系统及服务 在Linux系统中软件的安装方式和模式是什么 接下来针对安装及管理作此描述 学习路径 Linux系统与管理
  • Linux 信号学习

    Linux 信号学习 信号量的基本概念 信号产生的条件 信号如何被处理 信号的异步特质 信号的分类 可靠信号 不可靠信号 实时信号 非实时信号 常见信号与默认行为 信号处理 signal 函数 sigaction 函数 向进程发送信号 ki
  • 暑假补卷6——Linux线程

    上课笔记 需整理 线程互斥 mythread cc include
  • docker的服务编排

    docker 服务编排 docker compose命令 docker compose 网络管理 想学习更全面的docker知识可以点击右侧 Docker的概念及基本指令学习 全 docker 服务编排 docker 服务编排也叫docke
  • Linux系统中查询发行版本号以及内核版本的命令总结

    了解Linux发行版本的版本号是一项非常重要的事情 大多数软件对系统的版本都有要求 发行版本号与软件不匹配 软件将无法安装或者无法使用 这边集合市面上流行的Linux发行版本版本号查询方法 有了这边文章 老板再也不担心我装错软件了呢一 发行
  • VM VirtualBox运行Ubuntu系统卡顿提速(亲测有效,附图)

    VirtualBox运行Ubuntu系统卡顿提速 亲测有效 附图 亲测有效 卡顿明显减少 欣喜之余 记录备忘 原文教程见下方链接 原文没有图 我把自己操作时的截图放在下面 原文 VirtualBox虚拟机运行Ubuntu如何不卡 记得要把U
  • (Linux无线网卡WIFI上网 一 )USB-WIFI驱动移植

    导航 Linux无线网卡WIFI上网 一 USB WIFI驱动移植 Linux无线网卡WIFI上网 二 WPA SUPPLICANT Linux下的wifi管理工具移植 Linux无线网卡WIFI上网 三 嵌入式Linux下的WIFI使用
  • ping: www.baidu.com: Temporary failure in name resolution

    虚拟机突然访问不了百度了 查看发现网络都是好的 root zk02 ping www baidu com ping www baidu com Temporary failure in name resolution 经查修改下如下配置文件
  • openssl安装与使用

    文章目录 1 OpenSSL简介 2 OpenSSL安装 3 加密技术介绍 4 openssl 命令 4 1摘要命令 4 2对称加密命令 4 3非对称加密命令 4 3 1生成私钥 4 3 2提取公钥 4 3 3利用公钥加密 私钥解密数据 4
  • ubuntu 强制关闭卡死的pycharm

    ubuntu 环境是 16 04 终端输入 monitor 点击 System monitor 之后输入java 上图是已经删除过的 找到java之后右键点击kill OK
  • 【LINUX相关】生成随机数(srand、/dev/random 和 /dev/urandom )

    目录 一 问题背景 二 修改方法 2 1 修改种子 2 2 使用linux中的 dev urandom 生成随机数 三 dev random 和 dev urandom 的原理 3 1 参考连接 3 2 重难点总结 3 2 1 生成随机数的
  • shell test功能

    test测试功能 对于要测试系统上面某些文件或其相关属性时 可以使用test进行测试 test会根据相关功能返回True或False 测试文件类型test e filename 测试功能 意义 e 该文件是否存在 f 该文件名是否存在且为文
  • 暑假补卷5——进程信号

    信号入门 板书 1 生活角度的信号 你在网上买了很多件商品 再等待不同商品快递的到来 但即便快递没有到来 你也知道快递来临时 你该怎么处理快递 也就是你能 识别快递 当快递员到了你楼下 你也收到快递到来的通知 但是你正在打游戏 需5min之
  • navicat连接linux虚拟机上的mysql出现10060的错误解决

    https www cnblogs com mmzs p 9201558 html 我的是当时配置MySQL的时候在iptables文件里面添加了3306但是 我没有保存 所以没有监听到这个端口 弄了一中午 唉 最终通过上面的文章得以解决
  • Linux学习第17天:pinctrl和gpio子系统开发:由0到1

    Linux版本号4 1 15 芯片I MX6ULL 大叔学Linux 品人间百味 思文短情长 本篇笔记的题目为 pinctrl和gpio子系统开发 由0到1 做嵌入式系统开发 肯定经历过单片机 ARM Linux这么一个过程 这是一个8位单
  • Linux下查看磁盘使用率及文件和文件夹大小

    原文地址 http blog sina com cn s blog 4ab088470106ge0o html 大家在使用linux的过程中 或许遇到过数据无法入库 无法上传数据等等 这就要多长个心眼 去查看一下磁盘使用率和文件大小吧 这时
  • linux c 语言小结

    linux c 语言小结 gdb 使用 gdb是调试linux c语言代码的 所以要调试linux c语言 先要 gcc g 文件名 才能开始调试 gdb 命令 首先在命令行中输入 gdb 调试的基本代码 list 展示 s 进入函数内部

随机推荐

  • 远程命令执行/命令注入 之 命令连接符

    目录 一 理论 二 实践 windows 10 a b a b a b a b kali linux a b a b a b a b 一 理论 远程命令执行可以用到的命令连接符 windows系统和linux系统各有4个 其中3个是共有的
  • [ 网络 ] 应用层协议 —— HTTP协议

    目录 1 HTTP协议 1 1URL urlencode和urldecode 2 HTTP协议格式 HTTP请求 HTTP响应 3 告知服务器意图的HTTP方法 GET 获取资源 POST 传输实体主体 GET和POST的区别 使用Cook
  • idea 最干净的的主题 Obsidian!

    idea 最干净的的主题 Obsidian
  • NETGEAR拒绝连接请求_习惯了独来独往,该怎么与别人建立连接?

    亲爱的咨询师 您好 受我这种别扭的性格困扰好久了 不知道为什么 我不知道怎么跟别人建立亲密关系 在与别人相处的过程中 我总是把自己放在很低的位置 过分在意别人的想法 不敢表达自己的真实想法 说话总是顺着别人 点菜也会犹豫很久 不敢点自己喜欢
  • from pathlib import path_华丽的蜕变-使用Pathlib模块,文件操作So Easy!

    更多精彩内容 请关注微信公众号 python学习开发 前言 大多人处理文件用的最多的还是os模快吧 比如下面这样的操作 gt gt gt path rsplit maxsplit 1 0 或者写出下面这样长长的代码 gt gt gt os
  • VS2005下MFC开发的ActiveX控件的部分总结 inf 篇

    本博客转载CSDN网友http blog csdn net immc1979 archive 2007 04 20 1572222 aspx 本人觉得写得非常的实在 一看就是从实际经验中总结出来的 借鉴了 感谢immc1979 虽然微软对A
  • 1-OpenWrt编译过程-2

    前言 接触 op 已达四年 今年开始梳理整体所学 具体还参考了佐大的视频 对 op 缺乏系统知识的可以尝试 总体而言官方文档和源码是最好的教程 文章目录 编译OpenWrt 概述 1 更新安装所有可选的软件包 2 编译设置 make men
  • 用C语言解“两个数的简单计算器”题

    7 12 两个数的简单计算器 本题要求编写一个简单计算器程序 可根据输入的运算符 对2个整数进行加 减 乘 除或求余运算 题目保证输入和输出均不超过整型范围 输入格式 输入在一行中依次输入操作数1 运算符 操作数2 其间以1个空格分隔 操作
  • Eclipse+webservice简单实例搭建

    文章作为学习笔记和分享用 准备工作 下载安装eclipse和axis2 1 5 4 bin zip 最新版本的搭建有问题就选择了此版本 下载本地找一个目录解压 1 指定axis2路径 Window gt Preferences gt Web
  • blender学习记录1--界面,工具介绍

    1 大纲选项开关 此时camera cube light对应图中的物体 没点一个则会自动选中物体 上图先开始camera cube light后面什么选项都没有 在漏斗一样的按钮选中这4个 第一个小箭头 是物体不能被选中 第二个眼睛 将物体
  • 本人的java小小作品--计算器

    初学者 菜鸟 小小作品 只实现了最简单的加减乘除功能 望请各位牛人指导 代码如下 试问 下面红色字体代码部分 能不能精简 或是其他改进一下啊 太繁琐了那样写 import java awt import java awt event imp
  • 刷题day68:完全平方数

    题意描述 给你一个整数 n 返回 和为 n 的完全平方数的最少数量 完全平方数 是一个整数 其值等于另一个整数的平方 换句话说 其值等于一个整数自乘的积 例如 1 4 9 和 16 都是完全平方数 而 3 和 11 不是 思路 与零钱兑换完
  • kvm虚拟机vnc和spice配置

    一 简介 通过vnc或spice方式访问虚拟主机上的KVM虚拟机 可以直接通过图形化界面virt manager来设置 但此处通过xml配置文件修改 二 详解 1 VNC方式访问 vnc方式访问虚拟机不是在kvm虚拟机安装配置vnc服务器
  • 【华为OD统一考试A卷

    华为OD统一考试A卷 B卷 新题库说明 2023年5月份 华为官方已经将的 2022 0223Q 1 2 3 4 统一修改为OD统一考试 A卷 和OD统一考试 B卷 你收到的链接上面会标注A卷还是B卷 请注意 根据反馈 目前大部分收到的都是
  • Java将数字金额转换为中文大写

    import java math BigDecimal import java util regex Matcher import java util regex Pattern 2022 5 5 author lf public clas
  • vue对于时间的处理

    2023 08 05 11 25 45 假如这个就是我们要传的时间字符串 比如今天是2023 08 05 同一天 现在把这个时间字符串传入到 formatDate 这个方法 就会给你返回 11 25 比如今天是2023 08 06 前一天
  • 一文综述人脸检测算法(附资源)

    文章来源 SIGAI 本文共9400字 建议阅读10 分钟 本文将和大家一起回顾人脸检测算法的整个发展历史 导读 人脸检测是目前所有目标检测子方向中被研究的最充分的问题之一 它在安防监控 人证比对 人机交互 社交和娱乐等方面有很强的应用价值
  • mysql准确查询出以固定字符开头的数据

    在做开发过程中 我们经常会遇到多种支付方式 为了区分 我们可能会根据订单的前两位或者前几位固定值来区分 在这里我向大家推荐三种方法 使用LEFT函数 函数使用方法如下 str是原串字段 length是要提取的长度 这里只能是正整数 该字段是
  • 检测zookeeper和kafka是否正常

    cd dirname 0 source bash profile count zoo ps ef grep config zookeeper properties grep v grep wc l count kafka ps ef gre
  • 常见的八种导致 APP 内存泄漏的问题

    像 Java 这样具有垃圾回收功能的语言的好处之一 就是程序员无需手动管理内存分配 这减少了段错误 segmentation fault 导致的闪退 也减少了内存泄漏导致的堆空间膨胀 让编写的代码更加安全 然而 Java 中依然有可能发生内