Android App内存泄漏原理、检测及修改方案

2023-11-17

目录

JVM工作原理

运行时数据区

垃圾回收(Garbage Collection)

如何判断对象为垃圾对象?

垃圾回收算法

 四种引用类型

什么是内存泄露

为什么会有内存泄露

Android 中导致内存泄漏的常见场景 

检查策略

实例分析

内部类概念


要想知道Android App内存泄漏的根本原因,首先要了解Java虚拟机(Java Virtual Machine)的工作原理。

JVM工作原理

编写好的java源程序,通过java编译器javac编译成java虚拟机识别的class文件(字节码文件),然后由JVM中类加载器加载字节码文件,加载完毕之后再由JVM引擎去执行。

在加载完毕到执行过程中,JVM会将程序执行时用到的数据和相关信息存储在运行时数据区(Runtime Data Area),这块区域就是常说的JVM内存结构,垃圾回收也是作用在这块区域。

运行时数据区

程序计数器(Program Computer Register)

是块较小的内存空间,是当前线程所查找行的字节码的行号指示器,在虚拟机的概念模型里,字节码解释器的工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

Java虚拟机栈(Java Virtual Machine Stack)

这块区域是线程私有的,与线程同时创建,用于存储栈帧。java每个方法执行的时候都会同时创建一个栈帧(stack frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息,每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机中从入栈到出栈的过程。

本地方法栈(Native Method Stacks)
作用和虚拟机栈类似,虚拟机栈执行的是java方法,本地方法栈执行的是Native方法,本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。

Java
java堆是java虚拟机所管理内存最大、被所有线程共享的一块区域,目的是用来存放对象,基本上所有的对象实例和数组都在堆上分配(不是绝对)。java堆是垃圾回收器管理的主要区域。

方法区(Method Area)

用来存储已被java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码数据。方法区也称为“永久代”,这是因为垃圾回收器对方法区的垃圾回收比较少,主要是针对常量池的回收以及对类型的卸载,回收条件比较苛刻。对此内存未完全回收而导致的内存泄漏,最后当方法区无法满足内存分配时,将抛出OutOfMemoryError异常。

垃圾回收(Garbage Collection)

Java GC 实质上也就是一个运行在Java虚拟机(JVM)上的一个程序,它自动地管理着内存的使用,在适当的时机释放并回收无用的内存分配。

Java内存回收机制主要作用在Java堆和方法区。

如何判断对象为垃圾对象?

引用计数算法

给每一个创建的对象增加一个引用计数器,每当一个地方引用它时,这个计数器就加1;而当引用失效时,这个计数器就减1.当这个引用计数器值为0时,也就是说这个对象没有任何地方在使用它了,那么就是一个无效的对象,便可以进行垃圾回收了。

缺点:无法解决对象之间循环引用的问题。

根搜索算法

在主流的商用程序中,都是使用根搜索算法(GC Roots Tracing)来判定对象是否存活。

算法:通过一系列为“GC Roots”的对象作为终点,当一个对象到GC Roots之间无法通过引用到达时,那么该对象便可以进行回收了。

在java语言中,有4种对象可以作为GC Roots:

  1. 栈变量:虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 静态变量:方法区中的静态变量属性引用的对象
  3. 常量池:方法区中常量引用的对象
  4. JNI:本地方法栈中(JNI)(Native方法)的应用对象

垃圾回收算法

标记-清除算法
实现:分为标记和清除两个阶段,首先根据上面的根搜索算法标记出所有需要回收的对象,在标记完成后,然后统一回收掉所有被标记的对象。

缺点:

  1. 效率低:标记和清除这两个过程的效率都不高
  2. 容易产生内存碎片:因为内存的申请通常不是连续的,那么清除一些对象后,那么就会产生大量不连续的内存碎片,而碎片太多时,当有个大对象需要分配内存时,便会造成没有足够的连续内存分配而提前触发垃圾回收,甚至直接抛出OutOfMemoryException。

复制算法

实现:将可用内存按容量划分为大小相等的两块区域,每次只使用其中一块,当这一块内存用完了,就将还活着的对象复制到领一块区域上,然后再把已使用过的内存空间一次性清理掉。

优点:

  • 每次都是对其中一块内存进行回收,不用考虑内存碎片的问题,而内存分配时,只需要移动堆顶指针,按顺序进行分配即可,简单高效。

缺点:

  • 将内存分为两块,但是每次只能使用一块,即机器的一半内存是闲置的,资源严重浪费。并且如果对象存活率较高,每次都需要复制大量对象,效率也会变得很低。

标记-整理算法
实现:首先标记出所有存活的对象,然后让所有存活对象向一端进行移动,最后直接清理边界以外的内存。
局限性:只有对象存活率很高的情况下,使用该算法才会效率较高。

分代收集算法

实现:根据对象的存活周期不同将内存分为几块,然后不同区域采用不同的回收算法。

  1. 对于存活周期较短,每次都有大批对象死忙,只有少量存活的区域,采用复制算法,因为只需要付出少量存活对象的复制成本即可完成收集。
  2. 对于存活周期较长,没有额外空间进行分配担保的区域,采用标记-整理算法,或标记-清除算法。

  1. 堆由新生代和老年代两块区域组成,而新生代区域又分为三个部分:Eden、From Survivor、To Survivor,比例是8:1:1.
  2. 新生代采用复制算法,每次使用一块Eden区和一块Survivor区,当进行垃圾回收时,将Eden和一块Survivor区域的所有存活对象复制到另一块Survivor区域,然后清理掉之前存放对象的区域,依次循环。
  3. 老年代采用标记-清除或标记-整理算法,根据使用的垃圾回收器来判断。

 四种引用类型

  1. 强引用(StrongReference):JVM 宁可抛出 OOM ,也不会让 GC 回收具有强引用的对象;
  2. 软引用(SoftReference):只有在内存空间不足时,才会被回的对象;
  3. 弱引用(WeakReference):在 GC 时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存;
  4. 虚引用(PhantomReference):任何时候都可以被GC回收,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否存在该对象的虚引用,来了解这个对象是否将要被回收。可以用来作为GC回收Object的标志。

什么是内存泄露

简单来说就是:不应该被 GC Roots 访问到到的内存,仍能被访问到,GC Roots 误以为这块内存区域并不是垃圾,导致该回收的内存没被回收。

久而久之,内存泄露越来越严重,旧的垃圾内存得不到回收,新的垃圾内存不断增加,可用的内存也就越来越少。

JVM 为了申请新的内存空间,频繁触发 GC,程序执行效率将会受到影响,程序甚至直接抛出 Out Of Memory Exception 异常退出。

为什么会有内存泄露

obj2 对象引用 obj1 对象,obj2 的生命周期(t1-t4)比 obj1 的生命周期(t2-t3)长的多。当 obj1 没有被应用程序使用之后,obj2 仍然在引用着 obj1 ,这样,垃圾回收器就没办法将 obj1 对象从内存中移除,从而导致内存泄漏问题。

Android 中导致内存泄漏的常见场景 

原因

预防措施

资源释放问题:如 Cursor、IO 流的引用,资源得不到释放造成内存泄露

Cursor、IO流在使用完毕后及时close

Context引用问题:耗时线程持有普通Context

1.尽量使用 ApplicationContext , 因为 Application 的 Context 的生命周期比较长;

2.使 用 WeakReference 代 替 强 引 用 。比 如 可 以 使 用 WeakReference<Context> mContextRef;

static 关键字的使用问题:static 修饰的变量,它的生命周期与类一样

尽量避免 static 成员变量引用资源耗费过多的实例, 比如 Context,View。

非静态内部类:非静态内部类持有外部类的实例对象,如果内部类生命周期长于外部类就会导致外部类无法回收,造成泄漏,容易出现问题的有Handler,Runnable,Thread等

1.将内部类,改为静态内部类

2.在内部类内采用弱引用保存Context 引用。

图片过大导致OOM

1.等比例缩小图片

2.对图片采用软引用,及时地进行 recyle()操作

3.使用加载图片框架处理图片

构造Adapter 时,没有使用缓存的 convertView

尽量使用RecyclerView代替ListView

Activity结束时资源没有释放:如BroadcastReceiver,各类监听器

在 onPause()、onStop()、 onDestroy()方法中需要注意释放资源的情况

WebView造成的泄露

不再使用WebView对象时,调用它的destory()函数来销毁它

检查策略

  1. 可根据细分原因逐一检查,重点检查Handler,Runnable,Thread等的使用。
  2. 可使用LeakCanary进行检查。

LeakCanary使用方法:

  1. Gradle的dependencies中添加 debugImplementation ‘com.squareup.leakcanary:leakcanary-android:2.7’
  2. 进入各页面,然后退出
  3. adb shell am start -n “包名/leakcanary.internal.activity.LeakLauncherActivity"
  4. 确认是否有内存泄漏情况,如果有根据给出的信息进行确认

实例分析

  • Toast引起的内存泄漏

分析:从trace的最后记录看,为FrameLayout不能回收,即Toast.mNextView没有释放,再向上可以看到是ToastUtil.sToast是static变量

解决:在Toast显示之后设置sToast.setView(null)

  • Binding引起的内存泄漏

分析:从trace最后看,是ConstraintLayout无法释放,原因是DataBindingImpl的root还持有引用,再向上为mBinding持有引用

解决:在Fragment的onDestroyView时设置mBinding = null

  • Callback,Runnable等引起的内存泄漏

分析:从trace最后看为MessageViewModel没有被释放,原因往上看是有与MessageListCallback持有其引用。

分析:从代码看,是使用了匿名内部类,默认持有外部类引用

解决:修改为静态内部类

  • 静态类,静态变量引起的内存泄漏

分析:从trace最后记录看,CatalogFragment退出后没有被释放,往上看是由于LoadingAdapter持有了mBinding,而Loading又持有了LoadingAdapter,Loading是个单例,sDefault是静态变量。

解决:在Fragment的onDestroyView方法中重置Loading的adapter

内部类概念

在 Java 语言中,内部类是指在一个外部类的内部再定义一个类。而对于这个内部类来说,它可以是静态 static 的,也可以用(访问修饰符)public,protected,default 和 private 来修饰。(而包含这个内部类的外部类只能使用 public 和 default来进行修饰)。

而在字节码语言中,只有类的概念,没有外部类和内部类的概念,类只能使用 public 和 default 进行访问控制。

  • 成员内部类

在外部类的内部,定义的非静态的内部类,叫成员内部类 

  • 静态内部类

在外部类的内部,定义的静态的内部类,叫静态内部类(或叫嵌套类)。

  • 局部内部类(概念理解

①在外部类的实例方法内部的局部内部类;

②在外部类的静态方法内部的局部内部类。

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

Android App内存泄漏原理、检测及修改方案 的相关文章

  • 来自十六进制代码的 Apache POI XSSFColor

    我想将单元格的前景色设置为十六进制代码中的给定颜色 例如 当我尝试将其设置为红色时 style setFillForegroundColor new XSSFColor Color decode FF0000 getIndexed 无论我在
  • 需要使用 joda 进行灵活的日期时间转换

    我想使用 joda 解析电子邮件中的日期时间字符串 不幸的是我得到了各种不同的格式 例如 Wed 19 Jan 2011 12 52 31 0600 Wed 19 Jan 2011 10 15 34 0800 PST Wed 19 Jan
  • 自动生成Flyway的迁移SQL

    当通过 Java 代码添加新模型 字段等时 JPA Hibernate 的自动模式生成是否可以生成新的 Flyway 迁移 捕获自动生成的 SQL 并将其直接保存到新的 Flyway 迁移中 以供审查 编辑 提交到项目存储库 这将很有用 预
  • jar 中的 apklib 有什么优点?

    我正在关注这个问题 https stackoverflow com questions 6059502 whats the difference between apklib and jar files但它并没有完全回答我的问题 jar 中
  • 是否可以通过编程方式查找 logback 日志文件?

    自动附加日志文件以支持电子邮件会很有用 我可以以编程方式设置路径 如以编程方式设置 Logback Appender 路径 https stackoverflow com questions 3803184 setting logback
  • 套接字的读写如何同步?

    我们创建一个套接字 在套接字的一侧有一个 服务器 在另一侧有一个 客户端 服务器和客户端都可以向套接字写入和读取 这是我的理解 我不明白以下事情 如果服务器从套接字读取数据 它在套接字中是否只看到客户端写入套接字的内容 我的意思是 如果服务
  • 如何从 Facebook 邀请好友到 Android 应用程序? - 给出错误

    我正在开发一个 Android 应用程序 我正在努力将 邀请朋友 功能添加到我的应用程序中 它转到我的AppLinkUrl成功但显示错误 我的清单代码如下
  • 删除Android所有语言中的字符串

    我有一个包含多个翻译的应用程序 我想删除一些字符串 我怎样才能重构并删除它们一次 例如在默认情况下strings xml文件并自动将删除传播到其他翻译的其他 strings xml 文件 您可以通过 Android Studio 中的 翻译
  • 如何避免 ArrayIndexOutOfBoundsException 或 IndexOutOfBoundsException? [复制]

    这个问题在这里已经有答案了 如果你的问题是我得到了java lang ArrayIndexOutOfBoundsException在我的代码中 我不明白为什么会发生这种情况 这意味着什么以及如何避免它 这应该是最全面的典范 https me
  • 使用 Mockito 模拟某些方法,但不模拟其他方法

    有没有办法使用 Mockito 模拟类中的某些方法 而不模拟其他方法 例如 在这个 诚然是人为的 Stock我想嘲笑的班级getPrice and getQuantity 返回值 如下面的测试片段所示 但我想要getValue 执行乘法 如
  • Java实现累加器类,提供Collector

    A Collector具有三种通用类型 public interface Collector
  • HashMap 值需要不可变吗?

    我知道 HashMap 中的键需要是不可变的 或者至少确保它们的哈希码 hashCode 不会改变或与另一个具有不同状态的对象发生冲突 但是 HashMap中存储的值是否需要与上面相同 为什么或者为什么不 这个想法是能够改变值 例如在其上调
  • Android:确定 2.2 及更高版本上的摄像头数量

    我的应用程序需要在 Android 2 2 及更高版本上运行 我需要一种方法来确定可用摄像机的数量 有很多帖子解决了这个问题 但我找不到一个有效的 一种解决方案是简单地检测操作系统版本 任何 2 2 版本的设备都仅限于 1 个摄像头 即使该
  • java库维护数据库结构

    我的应用程序一直在开发 所以偶尔 当版本升级时 需要创建 更改 删除一些表 修改一些数据等 通常需要执行一些sql代码 是否有一个 Java 库可用于使我的数据库结构保持最新 通过分析类似 db structure version 信息并执
  • Spring-ws:如何从没有“Request”元素的 xsd 创建 Wsdl

    尝试为客户端实现 SOAP Web 服务 我需要一个 wsdl 文件来通过soapUI 测试该服务 但正如您在下面看到的 这个 xsd 没有 Request 和 Response 方法 所有请求和响应都被定义为基本 ServiceProvi
  • Hamcrest Matchers - 断言列表类型

    问题 我目前正在尝试使用 Hamcrest Matchers 来断言返回的列表类型是特定类型 例如 假设我的服务调用返回以下列表 List
  • 如何在Android中解析xml类型的HTTPResponse

    我有一个 Android 应用程序 我使用 POST 方法来获取响应 这是我的代码 HttpResponse httpResponse httpclient execute httppost HttpEntity resEntity htt
  • 如何重新启动死线程? [复制]

    这个问题在这里已经有答案了 有哪些不同的可能性可以带来死线程回到可运行状态 如果您查看线程生命周期图像 就会发现一旦线程终止 您就无法返回到新位置 So 没有办法将死线程恢复到可运行状态 相反 您应该创建一个新的 Thread 实例
  • org.apache.commons.net.io.CopyStreamException:复制时捕获 IOException

    我正在尝试使用以下方法中的代码将在我的服务器中创建的一些文件复制到 FTP 但奇怪的是我随机地低于错误 我无法弄清楚发生了什么 Exception org apache commons net io CopyStreamException
  • 具有矢量可绘制的 ImageView 的 Resources$NotFoundException

    我遇到了崩溃 Resources NotFoundException用于在活动创建时绘制的矢量 21 日前崩溃 安卓工作室2 1 支持库24 0 0 Gradle插件2 1 0 目标SDK 23 最小SDK 15 buildTools版本

随机推荐

  • 算法在ros中应用_烟火检测算法——中伟视界人工智能算法AI在智慧工地、石油中的应用_腾讯新闻...

    烟火检测算法功能说明及实现原理等 一 软件概述 视频智能分析基于目前先进的深度学习算法 通过大量的项目现场素材训练模型 通过本站大量采集的工作服素材 高精度的识别人 安全帽 工作服等识别 本项目主要两方面的算法 一是识别类的 二是行为分析
  • WPF中Datagrid其中一列使用图片显示

    实现效果 实现遇到的问题 当时想要实现如图所示 合格率 所示的效果 我的第一个想法就是使用wpf的转换器 可是接下来问题来了 我这个是通过数值来判断是否合格 什么控件可以做到既可以绑定图片类型的 又可以绑定数值类型的 还有此时的当值绑定肯定
  • 段、页、页框、页表、页表项

    段 页 页框 页表 页表项 分页式虚拟内存 页 页框 页表 页表项 段页式虚拟内存 分段 分页 段 段表 段表项 页 页框 页表 页表项 分页式虚拟内存 页 页框 页表 页表项 页 进程中的块 进程被分成许多大小相同的块 页号 页框 内存中
  • TS2769: Property 'xxx' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttribute...

    用TypeScript开发React项目 在父子组件间传值时发生错误提示 class Page extends React Component render return div div
  • vue组件利用css var(--变量)实现动态修改伪类属性(::before、::after)

    如图所示 1 我们可以利用此属性实现vue组件动态传值 修改例如 before after等 伪类的背景色 背景图等属性值 因为vue利用无法直接在css中使用data里的变量 利用var 变量名 以及style中定义变量 其实此步是模仿
  • Coordinate attention,SE,CBAM

    1 SE 因为普通卷积难以建模信道关系 SE考虑通道的相互依赖关系增强模型对信息通道的敏感性 同时全局平均池化可以帮助模型捕获全局信息 然而SE只考虑了内部通道信息而忽略了位置信息的重要性 输入X首先经过全局平均池化 然后经过全连接层来捕获
  • 静态类和动态类的区别和使用

    1 静态类中的静态方法可以通过类名直接调用静态方法 不需要实例化对象 但是无法和Spring容器中的bean进行交互 例如 Slf4j public class ExcelUtil public static
  • 动态链接

    动态链接 命令 gcc static 产生静态库 shared 产生共享库 readelf d 查看 dynamic段的内容 ldd 查看一个程序主模块或一个共享库依赖于哪些共享库 一 静态链接和动态链接的优缺点 静态链接 空间的浪费 静态
  • Arduino结合HX711实现8路信号采集称重

    说明 使用两块Arduino实现8路Sensor同时采集 并输出控制信号 写作目的主要是为了作为学习笔记 Arduino Sensor接线图 1 双机通讯连线图 2 HX711和Sensor的连线图 3 将8个Sensor的SCK全部接到r
  • 键盘输入_bp

    依据惯例 仍然感谢出处 来自程序员的暴击 https space bilibili com 128373173 学习了下 这个说了个什么呢 人到达灯附近 显示提示文字 按F键开灯和关灯切换 远离灯时 提示文字消失 不能切换灯的切换开关状态
  • QT编译报错无法解析的外部符号

    QT编译报错无法解析的外部符号 特征 头文件 有几个槽函数 提示有多少个无法解析的外部符号 注释掉宏Q OBJECT 可以编译通过 可能原因 1 对应的cpp文件没有加入项目中 2 cpp文件 右键属性 为 自定义工具 没有进行编译 修改为
  • 华为、华三、锐捷、飞塔、山石的抓包命令

    一 华为的抓包命令 1 基本概念 华为的抓包行为称之为镜像端口 也就是说将需要抓取的接口上 称为镜像端口 的流量复制一份到另一个接口上 工程师进行流量观察的端口 称为观察端口 如下图所示 2 华为镜像端口分类 1 本地镜像端口 也就是观察端
  • Django框架:优缺点、实用场景及与Flask、FastAPI的对比

    Django是一个使用Python语言编写的高级Web框架 它提供了快速开发 可重用和可维护的Web应用程序所需的一切组件 在本文中 我们将探讨Django的get和post请求 优缺点 实用场景以及与Flask FastAPI的对比 Dj
  • windows中如何将收藏夹里的下载链接加入到开始

    windows中如何将收藏夹里的下载链接加入到开始 以windows 7为例 设置方法如下 1 右击工具栏 属性 2 开始菜单 自定义 下拉至下载 点中显示为链接 确定 3 可以看到 下载已经看到了
  • C# 泛型详解(泛型类,方法,接口,委托,约束,反射 )

    目录 一 什么是泛型 二 为什么要用泛型 三 泛型和Object类型的区别 四 泛型类 五 泛型方法 六 泛型接口 七 泛型委托 八 泛型约束 九 泛型配合反射 结束 一 什么是泛型 先看一段介绍 泛型 Generic 是将不确定的类型预先
  • 天梯题集——多项式A除以B(多项式除法,递归与循环的效率比较)

    多项式A除以B 多项式除法 这里就不展开介绍多项式除法 只需将多项式看成一个整体就类似于整数除法 x3 1 x 1 x2 x 1 多项式除法的演示图 解题思路 模拟 A B 多项式除法 方案一 递归 include
  • 抗渗等级p6是什么意思_混凝土p6是什么意思

    展开全部 40是混凝土的强度等62616964757a686964616fe58685e5aeb931333431356664级 P6是抗渗混凝土按抗渗压力 抗渗混凝土按抗渗压力不同分为P6 P8 P10 P12和大于P12共5个等级 抗渗
  • 面试题目搜集(5)

    本博客的 面试题目搜集系列不错 1 面试题目搜集1 2 面试题目搜集2 3 面试题目搜集3 4 面试题目搜集4 5 面试题目搜集5 6 面试题目搜集6 1 反着打印链表 递归实现 include
  • 详解pytorch之tensor的拼接

    tensor经常需要进行拼接 拆分与调换维度 比如通道拼接 比如通道调至最后一个维度等 本文的目的是详细讨论一下具体是怎么拼接的 如果本来就理解这其中的原理的童鞋就不用往下看了 肯定觉得啰嗦了 拼接即两个tensor按某一维度进行拼接 分两
  • Android App内存泄漏原理、检测及修改方案

    目录 JVM工作原理 运行时数据区 垃圾回收 Garbage Collection 如何判断对象为垃圾对象 垃圾回收算法 四种引用类型 什么是内存泄露 为什么会有内存泄露 Android 中导致内存泄漏的常见场景 检查策略 实例分析 内部类