Android内存泄漏简介

前言java

   很多人认为JAVA程序,由于有垃圾回收机制,应该没有内存泄露。android

其实若是咱们一个程序中,已经再也不使用某个对象,可是由于仍然有引用指向它,垃圾回收器就没法回收它,固然该对象占用的内存就没法被使用,这就形成了内存泄露。若是咱们的java运行好久,而这种内存泄露不断的发生,最后就没内存可用了。固然java的,内存泄漏和C/C++是不同的。若是java程序彻底结束后,它全部的对象就都不可达了,系统就能够对他们进行垃圾回收,它的内存泄露仅仅限于它自己,而不会影响整个系统的。C/C++的内存泄露就比较糟糕了,它的内存泄露是系统级,即便该C/C++程序退出,它的泄露的内存也没法被系统回收,永远不可用了,除非重启机器。shell

Android的一个应用程序的内存泄露对别的应用程序影响不大。为了可以使得Android应用程序安全且快速的运行,Android的每一个应用程序都会使用一个专有的Dalvik虚拟机实例来运行,它是由Zygote服务进程孵化出来的,也就是说每一个应用程序都是在属于本身的进程中运行的。Android为不一样类型的进程分配了不一样的内存使用上限,若是程序在运行过程当中出现了内存泄漏的而形成应用进程使用的内存超过了这个上限,则会被系统视为内存泄漏,从而被kill掉,这使得仅仅本身的进程被kill掉,而不会影响其余进程(若是是system_process等系统进程出问题的话,则会引发系统重启)。数据库

1、引用没释放形成的内存泄露缓存

1.一、注册没取消形成的内存泄露安全

  这种Android的内存泄露比纯java的内存泄露还要严重,由于其余一些Android程序可能引用咱们的Anroid程序的对象(好比注册机制)。即便咱们的Android程序已经结束了,可是别的引用程序仍然还有对咱们的Android程序的某个对象的引用,泄露的内存依然不能被垃圾回收。ide

好比函数

    假设咱们但愿在锁屏界面(LockScreen)中,监听系统中的电话服务以获取一些信息(如信号强度等),则能够在LockScreen中定义一个PhoneStateListener的对象,同时将它注册到TelephonyManager服务中。对于LockScreen对象,当须要显示锁屏界面的时候就会建立一个LockScreen对象,而当锁屏界面消失的时候LockScreen对象就会被释放掉。布局

    可是若是在释放LockScreen对象的时候忘记取消咱们以前注册的PhoneStateListener对象,则会致使LockScreen没法被垃圾回收。若是不断的使锁屏界面显示和消失,则最终会因为大量的LockScreen对象没有办法被回收而引发OutOfMemory,使得system_process进程挂掉。测试

虽然有些系统程序,它自己好像是能够自动取消注册的(固然不及时),可是咱们仍是应该在咱们的程序中明确的取消注册,程序结束时应该把全部的注册都取消掉。

1.二、集合容器对象没清理形成的内存泄露

咱们一般把一些对象的引用加入到了集合容器(好比ArrayList)中,当咱们不须要该对象时,并无把它的引用从集合中清理掉,这样这个集合就会愈来愈大。若是这个集合是static的话,那状况就更严重了。

1.三、Context泄漏
所谓的Context泄漏,其实更多的是指Activity泄露,这是一个很隐晦的OutOfMemoryError的状况。
先看一个Android官网提供的例子: 
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);  
}  
这段代码效率很快,但同时又是极其错误的; 在第一次屏幕方向切换时它泄露了一开始建立的Activity。当一个Drawable附加到一个 View上时, View会将其做为一个callback设定到Drawable上。上述的代码片断,意味着这个静态的Drawable拥有一个TextView的引用, 
而TextView又拥有Activity(Context类型)的引用,换句话说,Drawable拥有了更多的对象引用。即便Activity被 销毁,内存仍然不会被释放。 
另外,对Context的引用超过它自己的生命周期,也会致使该Context没法回收,从而致使内存泄漏。因此尽可能使用Application这种Context类型。 
这种Context拥有和应用程序同样长的生命周期,而且不依赖Activity的生命周期。若是你打算保存一个长时间的对象, 
而且其须要一个 Context,记得使用Application对象。你能够经过调用Context.getApplicationContext()或 Activity.getApplication()轻松获得Application对象。 
最近遇到一种状况引发了Context泄漏,就是在Activity销毁时,里面有其余线程没有停。 
总结一下避免Context泄漏应该注意的问题: 
1.尽可能使用Application这种Context类型。 
2.注意对Context的引用不要超过它自己的生命周期。 
3.慎重的对Context使用“static”关键字。 
4.Context里若是有线程,必定要在onDestroy()里及时停掉。 
1.四、static关键字的滥用
 当类的成员变量声明成static后,它是属于类的而不是属于对象的,若是咱们将很大的资源对象(Bitmap,context等)声明成static,那么这些资源不会随着对象的回收而回收, 会一直存在,因此在使用static关键字定义成员变量的时候要慎重。
1.五、WebView对象没有销毁
当咱们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,不然其占用的内存长期也不能被回收,从而形成内存泄露
1.六、GridView的滥用
GridView和ListView的实现方式不太同样。GridView的View不是即时建立的,而是所有保存在内存中的。好比一个GridView有100项,虽然咱们只能看到10项,可是其实整个100项都是在内存中的。

2、资源对象没关闭形成的内存泄露

资源性对象好比(Cursor,File文件等)每每都用了一些缓冲,咱们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不只存在于java虚拟机内,还存在于java虚拟机外。若是咱们仅仅是把它的引用设置为null,而不关闭它们,每每会形成内存泄露。由于有些资源性对象,好比SQLiteCursor(在析构函数finalize(),若是咱们没有关闭它,它本身会调close()关闭),若是咱们没有关闭它,系统在回收它时也会关闭它,可是这样的效率过低了。所以对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,而后才置为null.在咱们的程序退出时必定要确保咱们的资源性对象已经关闭。

程序中常常会进行查询数据库的操做,可是常常会有使用完毕Cursor后没有关闭的状况。若是咱们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操做的状况下才会复现内存问题,这样就会给之后的测试和问题排查带来困难和风险。

3、一些不良代码成内存压力

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

3.一、Bitmap没调用recycle()

Bitmap对象在不使用时,咱们应该先调用recycle(),而后才它设置为null.

虽然Bitmap在被回收时能够经过BitmapFinalizer来回收内存。可是调用recycle()是一个良好的习惯

在Android4.0以前,Bitmap的内存是分配在Native堆中,调用recycle()能够当即释放Native内存。

从Android4.0开始,Bitmap的内存就是分配在dalvik堆中,即JAVA堆中的,调用recycle()并不能当即释放Native内存。可是调用recycle()也是一个良好的习惯。

能够经过dumpsys meminfo命令查看一个进程的内存状况。

示例:adb shell "dumpsys meminfo com.lenovo.robin"

运行结果。

Applications Memory Usage (kB):

Uptime: 18696550 Realtime: 18696541

 

** MEMINFO in pid 7985 [com.lenovo.robin] **

                    native   dalvik    other    total

            size:     4828     5379      N/A    10207

       allocated:     4073     2852      N/A     6925

            free:       10     2527      N/A     2537

           (Pss):      608      317     1603     2528

  (shared dirty):     2240     1896     6056    10192

    (priv dirty):      548       36     1276     1860

 

 Objects

           Views:        0        ViewRoots:        0

     AppContexts:        0       Activities:        0

          Assets:        2    AssetManagers:        2

   Local Binders:        5    Proxy Binders:       11

Death Recipients:        1

 OpenSSL Sockets:        0

 

 SQL

               heap:        0         MEMORY_USED:        0

 PAGECACHE_OVERFLOW:        0         MALLOC_SIZE:        0

关于内存统计的更多内容请参考《Android内存泄露利器(内存统计篇)

3.二、构造Adapter时,没有使用缓存的 convertView

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

public View getView(int position, View convertView, ViewGroup parent)

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

    由此能够看出,若是咱们不去使用convertView,而是每次都在getView()中从新实例化一个View对象的话,即浪费时间,也形成内存垃圾,给垃圾回收增长压力,若是垃圾回收来不及的话,虚拟机将不得不给该应用进程分配更多的内存,形成没必要要的内存开支。ListView回收list item的view对象的过程能够查看:

android.widget.AbsListView.java --> void addScrapView(View scrap) 方法。

示例代码

public View getView(int position, View convertView, ViewGroup parent) {

    View view = new Xxx(...);

    ... ...

    return view;

}

修正示例代码

public View getView(int position, View convertView, ViewGroup parent) {

    View view = null;

    if (convertView != null) {

        view = convertView;

        populate(view, getItem(position));

        ...

    } else {

        view = new Xxx(...);

        ...

    }

    return view;

3.三、ThreadLocal使用不当

若是咱们粗暴的把ThreadLocal设置null,而不调用remove()方法或set(null),那么就可能形成ThreadLocal绑定的对象长期也不能被回收,于是产出内存泄露。
关于此的更多内容请参考《ThreadLocal的内存泄露
4、JNI代码的内存泄露
关于此的详细内容请参考《JNI引用与垃圾回收
相关文章
相关标签/搜索