简析Android的垃圾回收与内存泄露

Android系统是运行在Java虚拟机上的,作为嵌入式设备,内存往往非常有限,了解Android的垃圾回收机制,可以有效的防止内存泄露问题或者OOM问题。本文作为入门文章,将浅显的讨论垃圾回收与内存泄露的原理,不讨论Dalvik虚拟机底层机制或者native层面的问题。

1. 基础

在分析垃圾回收前,我们要复习Java与离散数学的基础。

  • 实例化:对象是类的一个实例,创建对象的过程也叫类的实例化。对象是以类为模板来创建的。比如Car car = new Car();,我们就创造了一个Car的实例(Create new class instance of Car)
  • 引用:某些对象的实例化需要其它的对象实例,比如ImageView的实例化就需要Context对象,就是表示ImageView对于Context持有引用(ImageView holds a reference to Context)。
  • 有向图:在每条边上都标有有向线段的图称为有向图,Java中的garbage collection采用有向图的方式进行内存管理,箭头的方向表示引用关系,比如 B ← A ,就是B中需要A,B引用A。
  • 可达:有向图D={S,R}中,对于Si,Sj 属于S,如果从Si到Sj有任何一条通路存在,则可称Si可达Sj。也就是说,当B ← A中间箭头断了,就称作不可达,这时A就不可达B了。
  • Shallow heap 与 Retain heap 的对比
    • Shallow heap表示当前对象所消耗的内存
    • Retained heap表示当前对象所消耗的内存加上它引用的内存总合 

Google I/O 2011: Memory management for Android Apps

上图的橙色的Object是该有向图的起点,它的Shallow heap是100,而它的Retained heap是100 + 300 = 400。

2. 什么是垃圾回收

Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露和溢出的问题,也不需要像C程序员那样战战兢兢。这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制。概括地说,该机制对虚拟机中的内存进行标记,并确定哪些内存需要回收,根据一定的回收策略,自动的回收内存,永不停息(Nerver Stop)的保证虚拟机中的内存空间,防止出现内存泄露和溢出问题。

3. 什么情况需要垃圾回收

对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常GC采用有向图的方式记录并管理堆中的所有对象,通过这种方式确定哪些对象时“可达”,哪些对象时“不可达”。当对象不可达的时候,即对象不再被引用的时候,就会被垃圾回收。

网上有很多文档介绍可达的关系了,如图,在第六行的时候,o2改变了指向,Obj2就不再引用main的了,即他它们是不可达的,Obj2就可能在下次的GC中被回收。

developerWorks Java technology

4. 什么是内存泄露

当你不再需要某个实例后,但是这个对象却仍然被引用,防止被垃圾回收(Prevent from being bargage collected)。这个情况就叫做内存泄露(Memory Leak)。

下面将以How to Leak a Context: Handlers & Inner Classes这篇文章翻译为例,介绍一个内存泄露。

看如下的代码


  1. public class SampleActivity extends Activity { 
  2.  
  3.   private final Handler mLeakyHandler = new Handler() {    @Override 
  4.     public void handleMessage(Message msg) {      // ...  
  5.     } 
  6.   } 
  7. }  

当你打完这个代码后,IDE应该就会提醒你


  1. In Android, Handler classes should be static or leaks might occur. 

它到底是如何泄露的呢?

  1. 当你启动一个application时,它会自动在主线程创建一个Looper对象,用于处理Handler中的message。Looper实现了简单的消息队列,在循环中一个接一个的处理Message对象。大多数Application框架事件(比如Activity生命周期调用,按钮点击等)都在Message中,它们在Looper的消息队列中一个接一个的处理。注意Looper是存在于application整个生命周期中。
  2. 当你新建了一个handler对象后,它会被分配给Looper的消息队列。被发送到消息队列的Message将保持对Handler的引用,因为当消息队列处理到这个消息时,需要使用[Handler#handleMessage(Message)](http://developer.android.com/reference/android/os/Handler.html#handleMessage(android.os.Message)这个方法。(也就是说,只要没有处理到这个Message,Handler就一直在队列中被引用)
  3. 在java中,非静态的内部Class与匿名Class对它们外部的Class有强引用。static inner class除外。 

引用关系

现在,我们尝试运行如下代码


  1. public class SampleActivity extends Activity { 
  2.  
  3.   private final Handler mLeakyHandler = new Handler() {    @Override 
  4.     public void handleMessage(Message msg) {      // ... 
  5.     } 
  6.   }  @Override 
  7.   protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    // Post a message and delay its execution for 10 minutes. 
  8.     mLeakyHandler.postDelayed(new Runnable() {      @Override 
  9.       public void run() { /* ... */ } 
  10.     }, 1000 * 60 * 10);    // Go back to the previous Activity. 
  11.     finish(); 
  12.   } 
  13. }  

这个程序很简单,我们可以脑补一下,它应该是启动了又瞬间关闭,但是事实真的是关闭了吗?

稍有常识的人可以看出,它发送了一个Message,将在十分钟后运行,也就是说Message将被保持引用达到10分钟,这就照成了至少10分钟的内存泄露。

最后正确的代码如下


  1. ublic class SampleActivity extends Activity { 
  2.  
  3.   /** 
  4.    * Instances of static inner classes do not hold an implicit 
  5.    * reference to their outer class. 
  6.    */ 
  7.   private static class MyHandler extends Handler { 
  8.     private final WeakReference<SampleActivity> mActivity; 
  9.  
  10.     public MyHandler(SampleActivity activity) { 
  11.       mActivity = new WeakReference<SampleActivity>(activity); 
  12.     }    @Override 
  13.     public void handleMessage(Message msg) {      SampleActivity activity = mActivity.get();      if (activity != null) {        // ... 
  14.       } 
  15.     } 
  16.   }  private final MyHandler mHandler = new MyHandler(this);  /** 
  17.    * Instances of anonymous classes do not hold an implicit 
  18.    * reference to their outer class when they are "static". 
  19.    */ 
  20.   private static final Runnable sRunnable = new Runnable() {      @Override 
  21.       public void run() { /* ... */ } 
  22.   };  @Override 
  23.   protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    // Post a message and delay its execution for 10 minutes. 
  24.     mHandler.postDelayed(sRunnable, 1000 * 60 * 10);    // Go back to the previous Activity. 
  25.     finish(); 
  26.   } 
  27. }  

结论

  • GC是按照有向图是否可达来判断对象实例是否有用
  • 如果不在需要某个实例,却仍然被引用,这个情况叫做内存泄露
  • 匿名类/非静态类内部class会保持对它所在Activity的引用,使用时要注意它们的生命周期不能超过Activity,否则要用static inner class
  • 善于在Activy中的生命周期(比如onPause)中手动控制其他类的生命周期

最后再补充一下iOS的情况,iOS在新版的OC与Swift中,已经引入了新的内存管理体系ARC(auto reference counting,引用自动计数),C代码在编译时,编译器自动适时的添加释放内存的代码。

本文作者:佚名

来源:51CTO

时间: 2024-10-31 08:04:47

简析Android的垃圾回收与内存泄露的相关文章

JS闭包、作用域链、垃圾回收、内存泄露相关知识小结_javascript技巧

补充: 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. 闭包的特性 闭包有三个特性: 1.函数嵌套函数 2.函数内部可以引用外部的参数和变量 3.参数和变量不会被垃圾回收机制回收 闭包的定义及其优缺点 闭包 是指有权访问另一个函数作用域中的变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量 闭包的缺点就是常驻内存,会增大内存使用量,使用不当很容易造成内存泄露. 闭包是javascript

.NET深入学习笔记(3):垃圾回收与内存管理

今天抽空来讨论一下.Net的垃圾回收与内存管理机制,也算是完成上个<WCF分布式开发必备知识>系列后的一次休息吧.以前被别人面试的时候问过我GC工作原理的问题,我现在面试新人的时候偶尔也会问相关的问题.那么你是否也遇到这样的问题呢?比如你清楚.Net的垃圾回收机制吗?你能简述一下GC的工作原理吗?怎么样才能有效的管理内存呢?Using语句体内实例化的对象有什么作用?等等相关问题.下面我们就来详细讨论一下.相信你看完以后也可以面试别人. 本节的组织如下,1..Net的类型和内存分配2.GC垃圾收

《Android 网络开发与应用实战详解》——2.1节简析Android安装文件

2.1 简析Android安装文件 Android 网络开发与应用实战详解 当我们下载并安装Android后,会在其安装目录中看到一些安装文件.这些文件具体是干什么用的,了解这些对学习后面的开发知识十分有用.所以在本节的内容中将简要介绍这些安装文件的基本知识. 2.1.1 Android SDK目录结构 安装Android SDK后,其安装目录的具体结构如图2-1所示. temp:里面包含了一些常用的文件模板. tools:包含了一些通用的工具文件. usb_driver:包含了AMD64和X8

《Android多媒体应用开发实战详解:图像、音频、视频、2D和3D》——2.1节简析Android安装文件

2.1 简析Android安装文件 Android多媒体应用开发实战详解:图像.音频.视频.2D和3D 当下载并安装Android后,会在其安装目录中看到一些安装文件.了解这些文件具体是干什么用的,对于我们后面的驱动开发知识学习十分有用,所以在本节的内容中将简要介绍这些安装文件的基本知识. 2.1.1 Android SDK目录结构 安装Android SDK后,其安装目录的具体结构如图2-1所示. temp:里面包含了一些常用的文件模板. tools:包含了一些通用的工具文件. usb_dri

.NET 垃圾回收与内存泄漏

原文:.NET 垃圾回收与内存泄漏 > 前言相信大家一定听过,看过甚至遇到过内存泄漏.在 .NET 平台也一定知道有垃圾回收器,它可以让开发人员不必担心内存的释放问题,因为它会自定管理内存.但是在 .NET 平台下进行编程,绝对不会发生内存泄漏的问题吗?答案是否定的,就算有了自动内存管理的垃圾回收器,也会发生内存泄漏.本文就讨论下 .NET 平台的垃圾回收器是如何工作的,进而当我们在编写 .NET 程序时避免发生内存泄漏的问题. > 垃圾回收的基本概念"垃圾"指的是事先分配

避免 Android中Context引起的内存泄露_Android

Context是我们在编写Android程序经常使用到的对象,意思为上下文对象. 常用的有Activity的Context还是有Application的Context.Activity用来展示活动界面,包含了很多的视图,而视图又含有图片,文字等资源.在Android中内存泄露很容易出现,而持有很多对象内存占用的Activity更加容易出现内存泄露,开发者需要特别注意这个问题. 本文讲介绍Android中Context,更具体的说是Activity内存泄露的情况,以及如何避免Activity内存泄

避免 Android中Context引起的内存泄露

Context是我们在编写Android程序经常使用到的对象,意思为上下文对象. 常用的有Activity的Context还是有Application的Context.Activity用来展示活动界面,包含了很多的视图,而视图又含有图片,文字等资源.在Android中内存泄露很容易出现,而持有很多对象内存占用的Activity更加容易出现内存泄露,开发者需要特别注意这个问题. 本文讲介绍Android中Context,更具体的说是Activity内存泄露的情况,以及如何避免Activity内存泄

析JAVA之垃圾回收机制

本文为2010年编写,所以有很多看法不是很准确,有一定的参考价值,如需要更加深入细节,请参看,2012年编写的关于JVM的文章: 认识JVM--第一篇-对象生成&回收算法 认识JVM--第二篇-java对象内存模型 JVM第三篇(简单demo) 系统架构-性能篇章1(应用系统性能2-OOM&参数配置) 相继的还会有更多的java深入的知识和机制. 对于JAVA编程和很多类似C.C++语言有一个巨大区别就是内存不需要自己去free或者delete,而是由JVM垃圾回收机制去完成的.对于这个过

Android gc垃圾回收研究学习

尊重个人劳动成果,转载请注明出处:http://blog.csdn.net/hnulwt/article/details/44903331 文中很多内容说到了JVM,我想通过研究学习JVM来达到认识DVM的目的.为了严谨,查询了一下 JVM和DVM的不同点 1.Dalvik 和标准 Java 虚拟机(JVM)的首要差别 Dalvik 基于寄存器,而 JVM 基于栈.基于寄存器的虚拟机对于更大的程序来说,在它们编译的时候,花费的时间更短. 2.Dalvik 和 Java 字节码的区别 Dalvik