常见的八种导致 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 内存泄漏的问题 的相关文章

  • 智能家居网络系统的设计

    今天开始学习制作智能家居网络系统 这个项目相对简单的多 但对于新手还是有难度的 所以今天我写出来 尽我最大努力写详细一点 单本人能力实在是有限 文章中肯定会出现很多错误 望大家指出来 一定改正 讲解过程中所涉及全部代码下载地址 智能家居网络
  • Linux环境基础开发工具的使用:

    创建新用户 新建用户操作 删除用户 Linux器 vim使用 1 vim的基本概念 vim的三种模式 其实有好多模式 目前掌握这3种即可 分别是命令模式 command mode 插入模式 Insert mode 和底行模式 last li
  • iptables 规则管理

    参考 http www zsythink net archives 1517 有两台测试机 zk02 192 168 27 152 zk03 192 168 27 153 目录 1 增加规则 2 追加规则 1 增加规则 首先看一条命令 表示
  • 初步安装dns

    dns安装详解 dns的实现工具 bind 查找系统中的bind的rpm包 DNS服务器的监听端口 查看监听端口相关信息 dns的实现工具 bind 查找系统中的bind的rpm包 yum list grep bind bind x86 6
  • Ubuntu下使用ls命令显示文件颜色相关内容及修改

    lt 转载自 http pcyoyo com p 465 gt 在Ubuntu下 使用ls命令显示目录下文件及文件夹时会先显示不同颜色 如下图所示 如果知道了不同颜色分别代表的含义 那么对于我们查看目录下文件信息方便了很多 所以就搜索了一下
  • 【Linux】RPM软件包和Yum软件仓库、apt

    RPM 简介 RPM Package Manager RPM 包管理器 由红帽公司提出 Redhat SUSE 等系列采用 建立集中数据库 记录软件包安装 卸载等变化信息 分析软件包依赖关系 RPM 包 文件名特征 软件名 版本信息 操作系
  • docker的服务编排

    docker 服务编排 docker compose命令 docker compose 网络管理 想学习更全面的docker知识可以点击右侧 Docker的概念及基本指令学习 全 docker 服务编排 docker 服务编排也叫docke
  • 基础配置Tomcat及使用

    配置Tomcat 背景简介 目前很多网站由java编写 所以解析Java程序需要有相关的软件来编写完成 Tomcat是其中之一 Tomcat技术先进 性能稳定且免费 是目前比较流行的web应用服务器 Tomcat是一个轻量化级应用服务器 实
  • Linux dstat监控工具简讲

    1 小声哔哔 记得在19年的年末 我第一次接触sar命令时将其奉为经典 至今看来仍不为过 可见我之前的博客 运维入门必备Linux sar命令 说回今天我们的工具dstat 与sar命令很相像 都很全面且强大 但是dstat更类似于看板 可
  • ArchLinux的安装和配置

    文章目录 安装ArchLinux 分区 更换镜像源 下载arch软件 进入新系统 安装系统引导工具 为root设置密码 解除U盘挂载 重启虚拟机 配置ArchLinux 配置语言区域 配置时区 设置主机名 中文本地化配置 提前准备 虚拟机软
  • valgrind简介与使用

    一 valgrind简介 Valgrind是一款用于内存调试 内存泄漏检测以及性能分析 检测线程错误的软件开发工具 Valgrind 是运行在Linux 上的多用途代码剖析和内存调试软件 主要包括Memcheck Callgrind Cac
  • openssl安装与使用

    文章目录 1 OpenSSL简介 2 OpenSSL安装 3 加密技术介绍 4 openssl 命令 4 1摘要命令 4 2对称加密命令 4 3非对称加密命令 4 3 1生成私钥 4 3 2提取公钥 4 3 3利用公钥加密 私钥解密数据 4
  • Linux 内存分配/内存管理 相关接口

    Linux 内存分配 内存管理 相关接口 分配栈内存 alloca 分配堆内存 直接分配 malloc 分配初始化空间 calloc 分配对齐空间 posix memalign aligned alloc 过时 memalign 过时 va
  • Linux如何运行.Applmage文件

    1 什么是 Applmage文件 AppImage是新型的打包软件 它可以解决Linux上面的依赖问题 在使用上面相比其他的软件使用极为简单 所谓的 Applmage文件就是使用该打包软件打包出来的文件格式 2 怎么运行 Applmage文
  • linux常用库 对应函数

    1 include
  • 2021-Linux系统与管理 - (二)Linux系统命令【超详细】

    自说 Linux命令 Linux命令行的格式 Linux系统的基本命令 自说 学习Linux必定要学习的就是命令了 凡事都是一步一个脚印 这样才踏实 呢么在学习Linux这条道上 我们更加要循序渐进 先学会走再勇往直前 以下便是Linux命
  • 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系统与管理 - (六)用户与组❤

    自说 学习路径 用户管理 用户管理命令 组管理 组管理命令 目录和文件的权限 自说 在Windos系统中 用户的概念我们并不陌生 它是一种身份也是一种权限 不同的用户也相应有着不同的使用 下面细说下Linux中的用户与组 学习路径 Linu
  • linux 积累

    linux文件夹打包命令 tar 解包 tar xvf FileName tar 打包 tar cvf FileName tar DirName 注 tar是打包 不是压缩 gz 解压1 gunzip FileName gz 解压2 gzi

随机推荐

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

    目录 一 理论 二 实践 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 中依然有可能发生内