Java中关于内存泄漏出现的缘由以及如何避免内存泄漏(超详细版汇总上)

Android 内存泄漏总结

内存管理的目的就是让咱们在开发中怎么有效的避免咱们的应用出现内存泄漏的问题。内存泄漏你们都不陌生了,简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却再也不被使用致使 GC 不能回收。最近本身阅读了大量相关的文档资料,打算作个 总结 沉淀下来跟你们一块儿分享和学习,也给本身一个警示,之后 coding 时怎么避免这些状况,提升应用的体验和质量。java

我会从 java 内存泄漏的基础知识开始,并经过具体例子来讲明 Android 引发内存泄漏的各类缘由,以及如何利用工具来分析应用内存泄漏,最后再作总结。android

Java 内存分配策略

Java 程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配,对应的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)、栈区和堆区。程序员

  • 静态存储区(方法区):主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好,而且在程序整个运行期间都存在。算法

  • 栈区 :当方法被执行时,方法体内的局部变量(其中包括基础数据类型、对象的引用)都在栈上建立,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。由于栈内存分配运算内置于处理器的指令集中,效率很高,可是分配的内存容量有限。数据库

  • 堆区 : 又称动态内存分配,一般就是指在程序运行时直接 new 出来的内存,也就是对象的实例。这部份内存在不使用时将会由 Java 垃圾回收器来负责回收。编程

栈与堆的区别:

在方法体内定义的(局部变量)一些基本类型的变量和对象的引用变量都是在方法的栈内存中分配的。当在一段方法块中定义一个变量时,Java 就会在栈中为该变量分配内存空间,当超过该变量的做用域后,该变量也就无效了,分配给它的内存空间也将被释放掉,该内存空间能够被从新使用。api

堆内存用来存放全部由 new 建立的对象(包括该对象其中的全部成员变量)和数组。在堆中分配的内存,将由 Java 垃圾回收器来自动管理。在堆中产生了一个数组或者对象后,还能够在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,这个特殊的变量就是咱们上面说的引用变量。咱们能够经过这个引用变量来访问堆中的对象或者数组。数组

举个例子:缓存

public class Sample { int s1 = 0; Sample mSample1 = new Sample(); public void method() { int s2 = 1; Sample mSample2 = new Sample(); } } Sample mSample3 = new Sample();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

Sample 类的局部变量 s2 和引用变量 mSample2 都是存在于栈中,但 mSample2 指向的对象是存在于堆上的。 
mSample3 指向的对象实体存放在堆上,包括这个对象的全部成员变量 s1 和 mSample1,而它本身存在于栈中。网络

结论:

局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。—— 由于它们属于方法中的变量,生命周期随方法而结束。

成员变量所有存储与堆中(包括基本数据类型,引用和引用的对象实体)—— 由于它们属于类,类对象终究是要被new出来使用的。

了解了 Java 的内存分配以后,咱们再来看看 Java 是怎么管理内存的。

Java是如何管理内存

Java的内存管理就是对象的分配和释放问题。在 Java 中,程序员须要经过关键字 new 为每一个对象申请内存空间 (基本类型除外),全部的对象都在堆 (Heap)中分配空间。另外,对象的释放是由 GC 决定和执行的。在 Java 中,内存的分配是由程序完成的,而内存的释放是由 GC 完成的,这种收支两条线的方法确实简化了程序员的工做。但同时,它也加剧了JVM的工做。这也是 Java 程序运行速度较慢的缘由之一。由于,GC 为了可以正确释放对象,GC 必须监控每个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC 都须要进行监控。

监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象再也不被引用。

为了更好理解 GC 的工做原理,咱们能够将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每一个线程对象能够做为一个图的起始顶点,例如大多程序从 main 进程开始执行,那么该图就是以 main 进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。若是某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么咱们认为这个(这些)对象再也不被引用,能够被 GC 回收。 
如下,咱们举一个例子说明如何用有向图表示内存管理。对于程序的每个时刻,咱们都有一个有向图表示JVM的内存分配状况。如下右图,就是左边程序运行到第6行的示意图。

Java使用有向图的方式进行内存管理,能够消除引用循环的问题,例若有三个对象,相互引用,只要它们和根进程不可达的,那么GC也是能够回收它们的。这种方式的优势是管理内存的精度很高,可是效率较低。另一种经常使用的内存管理技术是使用计数器,例如COM模型采用计数器方式管理构件,它与有向图相比,精度行低(很难处理循环引用的问题),但执行效率很高。

什么是Java中的内存泄露

在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特色,首先,这些对象是可达的,即在有向图中,存在通路能够与其相连;其次,这些对象是无用的,即程序之后不会再使用这些对象。若是对象知足这两个条件,这些对象就能够断定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。

在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,而后却不可达,因为C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,所以程序员不须要考虑这部分的内存泄露。

经过分析,咱们得知,对于C++,程序员须要本身管理边和顶点,而对于Java程序员只须要管理边就能够了(不须要管理顶点的释放)。经过这种方式,Java提升了编程的效率。

所以,经过以上分析,咱们知道在Java中也有内存泄漏,但范围比C++要小一些。由于Java从语言上保证,任何对象都是可达的,全部的不可达对象都由GC管理。

对于程序员来讲,GC基本是透明的,不可见的。虽然,咱们只有几个函数能够访问GC,例如运行GC的函数System.gc(),可是根据Java语言规范定义, 该函数不保证JVM的垃圾收集器必定会执行。由于,不一样的JVM实现者可能使用不一样的算法管理GC。一般,GC的线程的优先级别较低。JVM调用GC的策略也有不少种,有的是内存使用到达必定程度时,GC才开始工做,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但一般来讲,咱们不须要关心这些。除非在一些特定的场合,GC的执行影响应用程序的性能,例如对于基于Web的实时系统,如网络游戏等,用户不但愿GC忽然中断应用程序执行而进行垃圾回收,那么咱们须要调整GC的参数,让GC可以经过平缓的方式释放内存,例如将垃圾回收分解为一系列的小步骤执行,Sun提供的HotSpot JVM就支持这一特性。

一样给出一个 Java 内存泄漏的典型例子,

Vector v = new Vector(10); for (int i = 1; i < 100; i++) { Object o = new Object(); v.add(o); o = null; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在这个例子中,咱们循环申请Object对象,并将所申请的对象放入一个 Vector 中,若是咱们仅仅释放引用自己,那么 Vector 仍然引用该对象,因此这个对象对 GC 来讲是不可回收的。所以,若是对象加入到Vector 后,还必须从 Vector 中删除,最简单的方法就是将 Vector 对象设置为 null。

详细Java中的内存泄漏

1.Java内存回收机制

不论哪一种语言的内存分配方式,都须要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址。Java中对象是采用new或者反射的方法建立的,这些对象的建立都是在堆(Heap)中分配的,全部对象的回收都是由Java虚拟机经过垃圾回收机制完成的。GC为了可以正确释放对象,会监控每一个对象的运行情况,对他们的申请、引用、被引用、赋值等情况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否能够达到,若是不可到达,则就将其回收,这样也能够消除引用循环的问题。在Java语言中,判断一个内存空间是否符合垃圾收集标准有两个:一个是给对象赋予了空值null,如下再没有调用过,另外一个是给对象赋予了新值,这样从新分配了内存空间。

2.Java内存泄漏引发的缘由

内存泄漏是指无用对象(再也不使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而形成内存空间的浪费称为内存泄漏。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。j

Java内存泄漏的根本缘由是什么呢?长生命周期的对象持有短生命周期对象的引用就极可能发生内存泄漏,尽管短生命周期对象已经再也不须要,可是由于长生命周期持有它的引用而致使不能被回收,这就是Java中内存泄漏的发生场景。具体主要有以下几大类:

一、静态集合类引发内存泄漏:

像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的全部的对象Object也不能被释放,由于他们也将一直被Vector等引用着。

例如

Static Vector v = new Vector(10); for (int i = 1; i<100; i++) { Object o = new Object(); v.add(o); o = null; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这个例子中,循环申请Object 对象,并将所申请的对象放入一个Vector 中,若是仅仅释放引用自己(o=null),那么Vector 仍然引用该对象,因此这个对象对GC 来讲是不可回收的。所以,若是对象加入到Vector 后,还必须从Vector 中删除,最简单的方法就是将Vector对象设置为null。

二、当集合里面的对象属性被修改后,再调用remove()方法时不起做用。

例如:

public static void main(String[] args) { Set<Person> set = new HashSet<Person>(); Person p1 = new Person("唐僧","pwd1",25); Person p2 = new Person("孙悟空","pwd2",26); Person p3 = new Person("猪八戒","pwd3",27); set.add(p1); set.add(p2); set.add(p3); System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:3 个元素! p3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变 set.remove(p3); //此时remove不掉,形成内存泄漏 set.add(p3); //从新添加,竟然添加成功 System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:4 个元素! for (Person person : set) { System.out.println(person); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

三、监听器

在java 编程中,咱们都须要和监听器打交道,一般一个应用当中会用到不少监听器,咱们会调用一个控件的诸如addXXXListener()等方法来增长监听器,但每每在释放对象的时候却没有记住去删除这些监听器,从而增长了内存泄漏的机会。

四、各类链接

好比数据库链接(dataSourse.getConnection()),网络链接(socket)和io链接,除非其显式的调用了其close()方法将其链接关闭,不然是不会自动被GC 回收的。对于Resultset 和Statement 对象能够不进行显式回收,但Connection 必定要显式回收,由于Connection 在任什么时候候都没法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会当即为NULL。可是若是使用链接池,状况就不同了,除了要显式地关闭链接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另一个也会关闭),不然就会形成大量的Statement 对象没法释放,从而引发内存泄漏。这种状况下通常都会在try里面去的链接,在finally里面释放链接。

五、内部类和外部模块的引用

内部类的引用是比较容易遗忘的一种,并且一旦没释放可能致使一系列的后继类对象没有释放。此外程序员还要当心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如: 
public void registerMsg(Object b); 
这种调用就要很是当心了,传入了一个对象,极可能模块B就保持了对该对象的引用,这时候就须要注意模块B 是否提供相应的操做去除引用。

六、单例模式

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

class A{
public A(){ B.getInstance().setA(this); } .... } //B类采用单例模式 class B{ private A a; private static B instance=new B(); public B(){} public static B getInstance(){ return instance; } public void setA(A a){ this.a=a; } //getter... } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

显然B采用singleton模式,它持有一个A对象的引用,而这个A类的对象将不能被回收。想象下若是A是个比较复杂的对象或者集合类型会发生什么状况

Android中常见的内存泄漏汇总


集合类泄漏

集合类若是仅仅有添加元素的方法,而没有相应的删除机制,致使内存被占用。若是这个集合类是全局性的变量 (好比类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,极可能致使集合所占用的内存只增不减。好比上面的典型例子就是其中一种状况,固然实际上咱们在项目中确定不会写这么 2B 的代码,但稍不注意仍是很容易出现这种状况,好比咱们都喜欢经过 HashMap 作一些缓存之类的事,这种状况就要多留一些心眼。

单例形成的内存泄漏

因为单例的静态特性使得其生命周期跟应用的生命周期同样长,因此若是使用不恰当的话,很容易形成内存泄漏。好比下面一个典型的例子,

public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context; } public static AppManager getInstance(Context context) { if (instance == null) { instance = new AppManager(context); } return instance; } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

这是一个普通的单例模式,当建立这个单例的时候,因为须要传入一个Context,因此这个Context的生命周期的长短相当重要:

一、若是此时传入的是 Application 的 Context,由于 Application 的生命周期就是整个应用的生命周期,因此这将没有任何问题。

二、若是此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,因为该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,因此当前 Activity 退出时它的内存并不会被回收,这就形成泄漏了。

正确的方式应该改成下面这种方式:

public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context.getApplicationContext();// 使用Application 的context } public static AppManager getInstance(Context context) { if (instance == null) { instance = new AppManager(context); } return instance; } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

或者这样写,连 Context 都不用传进来了:

在你的 Application 中添加一个静态方法,getContext() 返回 Application 的 context,

...

context = getApplicationContext();

...
   /** * 获取全局的context * @return 返回全局context对象 */ public static Context getContext(){ return context; } public class AppManager { private static AppManager instance; private Context context; private AppManager() { this.context = MyApplication.getContext();// 使用Application 的context } public static AppManager getInstance() { if (instance == null) { instance = new AppManager(); } return instance; } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

匿名内部类/非静态内部类和异步线程

非静态内部类建立静态实例形成的内存泄漏

有的时候咱们可能会在启动频繁的Activity中,为了不重复建立相同的数据资源,可能会出现这种写法:

public class MainActivity extends AppCompatActivity { private static TestResource mResource = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(mManager == null){ mManager = new TestResource(); } //... } class TestResource { //... } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这样就在Activity内部建立了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复建立,不过这种写法却会形成内存泄漏,由于非静态内部类默认会持有外部类的引用,而该非静态内部类又建立了一个静态的实例,该实例的生命周期和应用的同样长,这就致使了该静态实例一直会持有该Activity的引用,致使Activity的内存资源不能正常回收。正确的作法为:

将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,若是须要使用Context,请按照上面推荐的使用Application 的 Context。固然,Application 的 context 不是万能的,因此也不能随便乱用,对于有些地方则必须使用 Activity 的 Context,对于Application,Service,Activity三者的Context的应用场景以下:

其中: NO1表示 Application 和 Service 能够启动一个 Activity,不过须要建立一个新的 task 任务队列。而对于 Dialog 而言,只有在 Activity 中才能建立

匿名内部类

android开发常常会继承实现Activity/Fragment/View,此时若是你使用了匿名类,并被异步线程持有了,那要当心了,若是没有任何措施这样必定会致使泄露

public class MainActivity extends Activity { ... Runnable ref1 = new MyRunable(); Runnable ref2 = new Runnable() { @Override public void run() { } }; ... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

ref1和ref2的区别是,ref2使用了匿名内部类。咱们来看看运行时这两个引用的内存:

能够看到,ref1没什么特别的。

但ref2这个匿名类的实现对象里面多了一个引用:

this$0这个引用指向MainActivity.this,也就是说当前的MainActivity实例会被ref2持有,若是将这个引用再传入一个异步线程,此线程和此Acitivity生命周期不一致的时候,就形成了Activity的泄露。

Handler 形成的内存泄漏

Handler 的使用形成的内存泄漏问题应该说是最为常见了,不少时候咱们为了不 ANR 而不在主线程进行耗时操做,在处理网络任务或者封装一些请求回调等api都借助Handler来处理,但 Handler 不是万能的,对于 Handler 的使用代码编写一不规范即有可能形成内存泄漏。另外,咱们知道 Handler、Message 和 MessageQueue 都是相互关联在一块儿的,万一 Handler 发送的 Message 还没有被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。

因为 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的。所以这种实现方式通常很难保证跟 View 或者 Activity 的生命周期保持一致,故很容易致使没法正确释放。

举个例子:

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

在该 SampleActivity 中声明了一个延迟10分钟执行的消息 Message,mLeakyHandler 将其 push 进了消息队列 MessageQueue 里。当该 Activity 被 finish() 掉时,延迟执行任务的 Message 还会继续存在于主线程中,它持有该 Activity 的 Handler 引用,因此此时 finish() 掉的 Activity 就不会被回收了从而形成内存泄漏(因 Handler 为非静态内部类,它会持有外部类的引用,在这里就是指 SampleActivity)。

修复方法:在 Activity 中避免使用非静态内部类,好比上面咱们将 Handler 声明为静态的,则其存活期跟 Activity 的生命周期就无关了。同时经过弱引用的方式引入 Activity,避免直接将 Activity 做为 context 传进去,见下面代码:

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

综述,即推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空。

前面提到了 WeakReference,因此这里就简单的说一下 Java 对象的几种引用类型。

Java对引用的分类有 Strong reference, SoftReference, WeakReference, PhatomReference 四种。

在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大并且声明周期较长的对象时候,能够尽可能应用软引用和弱引用技术。

软/弱引用能够和一个引用队列(ReferenceQueue)联合使用,若是软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列能够得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。

假设咱们的应用会用到大量的默认图片,好比应用中有默认的头像,默认游戏图标等等,这些图片不少地方会用到。若是每次都去读取图片,因为读取文件须要硬件操做,速度较慢,会致使性能较低。因此咱们考虑将图片缓存起来,须要的时候直接从内存中读取。可是,因为图片占用内存空间比较大,缓存不少图片须要不少的内存,就可能比较容易发生OutOfMemory异常。这时,咱们能够考虑使用软/弱引用技术来避免这个问题发生。如下就是高速缓冲器的雏形:

首先定义一个HashMap,保存软引用对象。

private Map <String, SoftReference<Bitmap>> imageCache = new HashMap <String, SoftReference<Bitmap>> ();
  • 1

再来定义一个方法,保存Bitmap的软引用到HashMap。

使用软引用之后,在OutOfMemory异常发生以前,这些缓存的图片资源的内存空间能够被释放掉的,从而避免内存达到上限,避免Crash发生。

若是只是想避免OutOfMemory异常的发生,则可使用软引用。若是对于应用的性能更在乎,想尽快回收一些占用内存比较大的对象,则可使用弱引用。

另外能够根据对象是否常用来判断选择软引用仍是弱引用。若是该对象可能会常用的,就尽可能用软引用。若是该对象不被使用的可能性更大些,就能够用弱引用。

ok,继续回到主题。前面所说的,建立一个静态Handler内部类,而后对 Handler 持有的对象使用弱引用,这样在回收时也能够回收 Handler 持有的对象,可是这样作虽然避免了 Activity 泄漏,不过 Looper 线程的消息队列中仍是可能会有待处理的消息,因此咱们在 Activity 的 Destroy 时或者 Stop 时应该移除消息队列 MessageQueue 中的消息。

下面几个方法均可以移除 Message:

public final void removeCallbacks(Runnable r); public final void removeCallbacks(Runnable r, Object token); public final void removeCallbacksAndMessages(Object token); public final void removeMessages(int what); public final void removeMessages(int what, Object object);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

尽可能避免使用 static 成员变量

若是成员变量被声明为 static,那咱们都知道其生命周期将与整个app进程生命周期同样。

这会致使一系列问题,若是你的app进程设计上是长驻内存的,那即便app切到后台,这部份内存也不会被释放。按照如今手机app内存管理机制,占内存较大的后台进程将优先回收,yi’wei若是此app作过进程互保保活,那会形成app在后台频繁重启。当手机安装了你参与开发的app之后一晚上时间手机被消耗空了电量、流量,你的app不得不被用户卸载或者静默。

这里修复的方法是:

不要在类初始时初始化静态成员。能够考虑lazy初始化。 
架构设计上要思考是否真的有必要这样作,尽可能避免。若是架构须要这么设计,那么此对象的生命周期你有责任管理起来。

避免 override finalize()

一、finalize 方法被执行的时间不肯定,不能依赖与它来释放紧缺的资源。时间不肯定的缘由是: 
虚拟机调用GC的时间不肯定 
Finalize daemon线程被调度到的时间不肯定

二、finalize 方法只会被执行一次,即便对象被复活,若是已经执行过了 finalize 方法,再次被 GC 时也不会再执行了,缘由是:

含有 finalize 方法的 object 是在 new 的时候由虚拟机生成了一个 finalize reference 在来引用到该Object的,而在 finalize 方法执行的时候,该 object 所对应的 finalize Reference 会被释放掉,即便在这个时候把该 object 复活(即用强引用引用住该 object ),再第二次被 GC 的时候因为没有了 finalize reference 与之对应,因此 finalize 方法不会再执行。

三、含有Finalize方法的object须要至少通过两轮GC才有可能被释放。

资源未关闭形成的内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,不然这些资源将不会被回收,形成内存泄漏。

一些不良代码形成的内存压力

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

好比: 
Bitmap 没调用 recycle()方法,对于 Bitmap 对象在不使用时,咱们应该先调用 recycle() 释放内存,而后才它设置为 null. 由于加载 Bitmap 对象的内存空间,一部分是 java 的,一部分 C 的(由于 Bitmap 分配的底层是经过 JNI 调用的 )。 而这个 recyle() 就是针对 C 部分的内存释放。 
构造 Adapter 时,没有使用缓存的 convertView ,每次都在建立新的 converView。这里推荐使用 ViewHolder。

总结

对 Activity 等组件的引用应该控制在 Activity 的生命周期以内; 若是不能就考虑使用 getApplicationContext 或者 getApplication,以免 Activity 被外部长生命周期的对象引用而泄露。

尽可能不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context ),即便要使用,也要考虑适时把外部成员变量置空;也能够在内部类中使用弱引用来引用外部类的变量。

对于生命周期比Activity长的内部类对象,而且内部类中使用了外部类的成员变量,能够这样作避免内存泄漏:

将内部类改成静态内部类
    静态内部类中使用弱引用来引用外部类的成员变量
  • 1
  • 2
  • 3

Handler 的持有的引用对象最好使用弱引用,资源释放时也能够清空 Handler 里面的消息。好比在 Activity onStop 或者 onDestroy 的时候,取消掉该 Handler 对象的 Message和 Runnable.

在 Java 的实现过程当中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,好比使用完Bitmap 后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ; array = null)等,最好遵循谁建立谁释放的原则。

正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。

保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。

相关文章
相关标签/搜索