内存泄露从入门到精通三部曲之常见原因与用户实践

腾讯Bugly特约作者: 姚潮生

常见原因

1.集合类

集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。

2.单例模式

不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在 JVM 的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被 JVM 正常回收,导致内存泄露

3.Android组件或特殊集合对象的使用

BraodcastReceiver,ContentObserver,FileObserver,Cursor,Callback等在 Activity onDestroy 或者某类生命周期结束之后一定要 unregister 或者 close 掉,否则这个 Activity 类会被 system 强引用,不会被内存回收。

不要直接对 Activity 进行直接引用作为成员变量,如果不得不这么做,请用 private WeakReference mActivity 来做,相同的,对于Service 等其他有自己声明周期的对象来说,直接引用都需要谨慎考虑是否会存在内存泄露的可能。

4. Handler

要知道,只要 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。由于 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的。因此这种实现方式一般很难保证跟 View 或者 Activity 的生命周期保持一致,故很容易导致无法正确释放。如上所述,Handler 的使用要尤为小心,否则将很容易导致内存泄露的发生。

5.Thread 内存泄露

线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。比如线程是 Activity 的内部类,则线程对象中保存了 Activity 的一个引用,当线程的 run 函数耗时较长没有结束时,线程对象是不会被销毁的,因此它所引用的老的 Activity 也不会被销毁,因此就出现了内存泄露的问题。

6.一些不良代码造成的内存压力

有些代码并不造成内存泄露,但是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存。

6.1 Bitmap 没调用 recycle().
Bitmap 对象在不使用时,我们应该先调用 recycle() 释放内存,然后才它设置为 null. 因为加载 Bitmap 对象的内存空间,一部分是 java 的,一部分 C 的(因为 Bitmap 分配的底层是通过 JNI 调用的 )。 而这个 recyle() 就是针对 C 部分的内存释放。

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


以业务测试过程中常见的部分内存泄露实例来说明:

1. callback只有add操作,没有注销remove

从引用关系可以看到当前 view 被 callback 引用,而 callback 被外部对象 sharkprotocolQueue 持有引用而导致泄漏。

2. 发送延时消息时,如果该消息未处理,在退出页面后会导致该页面无法回收。

Android 应用启动的时候会创建 UI 主线程的 Looper 对象,它存在于整个应用的生命周期,用于处理消息队列里的 Message。而这些 Message 会引用发送该消息的 Handler 对象。

那么问题来了,如果这些 Handler 是 Activity 的内部类,那么当这些 Handler 的消息未处理完或者消息本身是延时消息的话,就会导致 Activity 退出后,从 Activity 到 Handler 到 Message 到 Looper 的引用链条一直存在,从而导致 Activity 的泄露!

3. 异步线程未完成前退出 Activity 等组件,可能会导致界面资源无法释放。

这种情况是典型的线程对象导致的内存泄露。原因也很简单,线程 Thread 对象的 run 任务未执行完之前,对象本身是不会释放的。因此 Activity 等组件对象内的线程对象成员如果有耗时任务(一般也都是耗时任务),就会导致一直持有组件本身的引用内存泄露!

本文部分内容和经验摘自网络,结合本次内存泄露的排查总结予以归纳。


优秀实践

  1. 对activity等组件的引用应该控制在activity的生命周期之内; 如果不能就考虑使用
    getApplicationContext或者getApplication,以避免activity被外部长生命周期的对象引用而泄露
  2. 在代码复审的时候关注长生命周期对象:全局性的集合、单例模式的使用、类的static变量等等。
  3. 尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量;
  4. Handler的持有的引用对象最好使用弱引用,资源释放时也可以清空Handler里面的消息。比如在Activity onStop或者onDestroy的时候,取消掉该Handler对象的Message和Runnable:
  5. removeCallbacks(Runnable r)或removeMessages(int what),或removeCallbacksAndMessages(null)等。
  6. 线程Runnable执行耗时操作,注意在页面返回时及时取消或者把Runnable写成静态类。
    a) 如果线程类是内部类,改为静态内部类。
    b) 线程内如果需要引用外部类对象如context,需要使用弱引用。
  7. 在Java的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋空,如清空对图片等资源有直接引用或者间接引用的数组(使用array.clear();array = null),最好遵循谁创建谁释放的原则。

腾讯Bugly简介

Bugly是腾讯内部产品质量监控平台的外发版本,其主要功能是App发布以后,对用户侧发生的Crash以及卡顿现象进行监控并上报,让开发同学可以第一时间了解到App的质量情况,及时机型修改。目前腾讯内部所有的产品,均在使用其进行线上产品的崩溃监控。

?

时间: 2024-10-26 16:45:28

内存泄露从入门到精通三部曲之常见原因与用户实践的相关文章

内存泄露从入门到精通三部曲之排查方法篇

内存泄露从入门到精通三部曲之排查方法篇 最原始的内存泄露测试 重复多次操作关键的可疑的路径,从内存监控工具中观察内存曲线,是否存在不断上升的趋势且不会在程序返回时明显回落.这种方式可以发现最基本,也是最明显的内存泄露问题,对用户价值最大,操作难度小,性价比极高. MAT内存分析工具 2.1 MAT分析heap的总内存占用大小来初步判断是否存在泄露 在Devices 中,点击要监控的程序. 点击Devices视图界面中最上方一排图标中的“Update Heap” 点击Heap视图 点击Heap视图

内存泄露从入门到精通三部曲之基础知识篇

作者:腾讯Bugly特约嘉宾:姚潮生 一.首先以一个内存泄露实例来开始本节基础概念的内容: 实例1:(单例导致内存对象无法释放而泄露) 可以看出ImageUtil这个工具类是一个单例,并引用了activity的context. 试想这个场景,应用起来以后,转屏.转屏以后,旧MainActivity会destroy,新MainActivity会重建,导致单例ImageUtil重新getInstance.很不幸的是,由于instance已经不是空的了,所以ImageUtil不会重建,还持有之前的Co

java 内存泄露

摘要 说起Java的内存泄露,其实定义不是那么明确.首先,如果JVM没有bug,那么理论上是不会出现“无法回收的堆空间”,也就是说C/C++中的那种内 存泄露在Java中不存在的.其次,如果由于Java程序一直持有某个对象的引用,但是从程序逻辑上看,这个对象再也不会被用到了,那么我们可以认为这个 对象被泄露了.如果这样的对象数量很多,那么很明显,大量的内存空间就被泄露(“浪费”更准确一些)了. 目录[-] 分析内存泄露的一般步骤 dump heap analyze heap 原因解释 解决方案

搜索总结c++ 内存泄露问题

师兄的项目上现在存在很多的料想不到的错误,在交流过程中,他说很多都是绕过去了,没有仔细的去纠察原因.于是我就在想内存泄露的问题影响到了项目出现了未知的错误,搜索关键词C++内存泄露,有很多前辈总结出来常见的容易出现内存泄露的地方,我在阅读的时候,结合自己的写代码习惯,还真了解不少我在代码编写过程中出现的问题,下面贴出搜索到的关键部分: http://blog.csdn.net/wenhm/article/details/4314863 指针成员变量分配了内存一定要释放: ~CApple() {

记一次内存泄露优化过程

背景 项目目前存在使用久了或者重复打开关闭某个页面,内存会一直飙升,居高不下,频繁发生GC.静置一段时间后,情况有所改善,但是问题依旧明显,如图1-1.1-2. 图1-1.操作时的内存使用情况 图1-2.静置时的内存使用情况 如上图1-1,是通过Android Studio查看内存(灰色)和CPU(红色)使用情况,可以看出内存有发生抖动并且是处于比较高的状态,再者,从logcat可以看到一直发生GC,如下图1-3: 图1-3. 出现这些情况,是有很多因素造成的,最主要的原因是发生了内存泄露:页面

Android 常见内存泄露 & 解决方案

前言 内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃 (OOM) 等严重后果. 那什么情况下不能被回收呢? 目前 java 垃圾回收主流算法是虚拟机采用 GC Roots Tracing 算法.算法的基本思路是:通过一系列的名为 GC Roots (GC 根节点)的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径,当一个对象到GC Roots没有任何引用链相连(图论说:从GC Roots

利用MAT分析JVM内存问题,从入门到精通(二)

上一篇文章MAT入门到精通(一)介绍了MAT的使用场景和基本概念,这篇文章开始介绍MAT的基本功能,后面还有两篇,一篇是MAT的高级功能,另一篇是MAT实战案例分析. 三.欢迎页 使用MAT打开一个heap dump文件,解析完成后,默认会进入欢迎页,欢迎页里包含了一些常见的分析:最大内存占用分析.常见的分析动作.常用的分析报告.MAT使用教程等等. 我们看下下面这张图,可以看出MAT的主要结构和功能: inspector:透视图,用于展示一个对象的详细信息,例如内存地址.加载器名称.包名.对象

CUDA从入门到精通

CUDA从入门到精通(零):写在前面 在老板的要求下,本博主从2012年上高性能计算课程开始接触CUDA编程,随后将该技术应用到了实际项目中,使处理程序加速超过1K,可见基于图形显示器的并行计算对于追求速度的应用来说无疑是一个理想的选择.还有不到一年毕业,怕是毕业后这些技术也就随毕业而去,准备这个暑假开辟一个CUDA专栏,从入门到精通,步步为营,顺便分享设计的一些经验教训,希望能给学习CUDA的童鞋提供一定指导.个人能力所及,错误难免,欢迎讨论. PS:申请专栏好像需要先发原创帖超过15篇...

Hbase从入门到精通_如何学好Hbase

Hbase从入门到精通 课程学习地址:http://www.xuetuwuyou.com/course/188 课程出自学途无忧网:http://www.xuetuwuyou.com 课程简介 面对海量数据的存储及实时查询,传统的RDBMS已经无法满足,基于HDFS之上的HBase应运而生,每个表的数据可以达到数百万列和数十亿条,数据存储在HDFS之上充分利用其存储优势,分布式的架构让其查询数据更加快,绝大数电商互联网公司都是用它.   课程内容 (1)HBase 初窥使用 HBase 应用场景