一. 内存泄漏定义

内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏了。

二. 内存泄漏对应用的影响

在android里面,出现内存泄漏会导致系统为应用分配的内存会不断减少,从而造成app在运行时会出现卡断(内存占用高时JVM虚拟机会频繁触发GC),影响用户体验。同时,可能会引起OOM(内存溢出),从而导致应用程序崩溃!

三. 引发原因

**1. ******非静态内部类的静态实例容易造成内存泄漏****

实例:

public class MainActivity extends Activity{
static Demo sInstance = null;
@Override
public void onCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (sInstance == null){
sInstance= new Demo();
}
}
class Demo{
voiddoSomething(){
System.out.print("dosth.");
}
}
}

分析:

上面的代码中的sInstance实例类型为静态实例,在第一个MainActivity act1实例创建时,sInstance会获得并一直持有act1的引用。当MainAcitivity销毁后重建,因为sInstance持有act1 的引用,所以act1是无法被GC回收的,进程中会存在2个MainActivity实例(act1和重建后的MainActivity实例),这个 act1对象就是一个无用的但一直占用内存的对象,即无法回收的垃圾对象。所以,对于lauchMode不是singleInstance的 Activity, 应该避免在activity里面实例化其非静态内部类的静态实例。

解决方法:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,就使用Application的Context。

**2. A******ctivity********使用静态成员****

实例:

private static Drawable sBackground;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}

分析:

由于用静态成员sBackground 缓存了drawable对象,所以activity加载速度会加快,但是这样做是错误的。因为在Android 2.3系统上,它会导致activity销毁后无法被系统回收。

label .setBackgroundDrawable函数调用会将label赋值给sBackground的成员变量mCallback。

上面代码意味着:sBackground(GC Root)会持有TextView对象,而TextView持有Activity对象。所以导致Activity对象无法被系统回收。

避免方法:

·不要对activity的context长期引用(一个activity的引用的生存周期应该和activity的生命周期相同)

·如果可以的话,尽量使用关于application的context来替代和activity相关的context

·如果一个acitivity的非静态内部类的生命周期不受控制,那么避免使用它;正确的方法是使用一个静态的内部类,并且对它的外部类有一WeakReference,就像在ViewRootImpl中内部类W所做的那样。

****3. ******单例造成的内存泄漏**

由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。

这样不管传入什么Context最终将使用Application的Context,而单例的生命周期和应用的一样长,这样就防止了内存泄漏。

****4. ******Handler****造成的内存泄漏**

示例:创建匿名内部类的静态对象

分析:

当MainActivity结束时,未处理的消息持有handler的引用,而handler又持有它所属的外部类也就是 MainActivity的引用。这条引用关系会一直保持直到消息得到处理,这样阻止了MainActivity被垃圾回收器回收,从而造成了内存泄漏。

解决方法:

将Handler类独立出来或者使用静态内部类,这样便可以避免内存泄漏。

****5. ******资源未关闭造成的内存泄漏**

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

1)比如在Activity中register了一个BraodcastReceiver,但在Activity结束后没有unregister该BraodcastReceiver。 2) 资源性对象比如Cursor,Stream、File文件等往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它 们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。 3)对于资源性对象在不使用的时候,应该调用它的close()函数将其关闭掉,然后再设置为null。在我们的程序退出时一定要确保我们的资源性对象已经关闭。 4)Bitmap对象不在使用时调用recycle()释放内存。2.3以后的bitmap应该是不需要手动recycle了,内存已经在java层了。

****6. ******线程造成的内存泄漏**

示例:AsyncTask和Runnable

分析:

AsyncTask和Runnable都使用了匿名内部类,那么它们将持有其所在Activity的隐式引用。如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。

解决方法:

将AsyncTask和Runnable类独立出来或者使用静态内部类,这样便可以避免内存泄漏。

****7. ******使用****ListView****时造成的内存泄漏**

初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的View对象,同时ListView会将这些View对象 缓存起来。当向上滚动ListView时,原先位于最上面的Item的View对象会被回收,然后被用来构造新出现在下面的Item。这个构造过程就是由 getView()方法完成的,getView()的第二个形参convertView就是被缓存起来的Item的View对象(初始化时缓存中没有 View对象则convertView是null)。

构造Adapter时,没有使用缓存的convertView。 解决方法:在构造Adapter时,使用缓存的convertView。

****8. ******集合容器中的内存泄露**

我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。 解决方法:在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。

****9.******WebView****造成的泄露**

当我们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其长期占用的内存也不能被回收,从而造成内存泄露。 解决方法:为WebView另外开启一个进程,通过AIDL与主线程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。

10. ****一些不良代码成内存压力

有些代码并不造成内存泄露,但是它们或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存,对内存的回收和分配造成很大影响的,容易迫使虚拟机不得不给该应用进程分配更多的内存,增加vm的负担,造成不必要的内存开支。

10.1 . Bitmap使用不当

第一、及时的销毁。

虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过Java堆的限制。因此,在用完Bitmap时,要 及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”。

第二、设置一定的采样率。

有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存

第三、巧妙的运用软引用(SoftRefrence)

有些时候,我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放。

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

以构造ListView的BaseAdapter为例,在BaseAdapter中提共了方法:

public View getView(intposition, View convertView, ViewGroup parent)

来向ListView提供每一个item所需要的view对象。

如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费时间,也造成内存垃圾,给垃圾回收增加压力,如果垃圾回收来不及的话,虚拟机将不得不给该应用进程分配更多的内存,造成不必要的内存开支。

10.3、不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。可以适当的使用 hashtable , vector 创建一组对象容器,然后从容器中去取那些对象,而不用每次 new 之后又丢弃。