欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

android开发--内存泄露

程序员文章站 2022-03-29 08:13:06
...

内存泄露

在Android中GC采用了标注和清理(Mark and
sweep)回收算法:从” GC Roots“集合开始,将内存中存活的对象整个遍历一遍,凡是能够被GC Roots直接或者间接引用到的对象保存起来,而剩下的孤魂野鬼的对象便被当成垃圾回收掉了。下面我画几个图展示一下GC的回收过程:

android开发--内存泄露
黄色圆形表示遍历后GC Roots与该对象存在可以到达的路径,深蓝色代表无法到达的路径,意味着该对象不存在引用即将被回收,释放占用的资源。
内存泄露:不再使用的对象,通过gc回收算法还可以到达。

Android的4种引用类型

  • Strong ref:强引用,被引用的对象在gc的时候不会被回收。
  • Soft ref:软引用,它引用的东西会在触发OOM的时候释放掉,换句话说,soft引用的东西和普通的没区别,但是当内存紧张的时候,可能会被释放掉。
  • Weak ref: 弱引用,当一个对象只有弱引用引用时,会在gc的时候被回收掉。
  • Phantom ref:虚引用,总之就是有点玄学,还不知道具体啥用。

强引用 (StrongReference)

Object mObject = new Object();

软引用 (SoftReference)

ReferenceQueue<String> queue = new ReferenceQueue<Object>();
SoftReference<RefObj> ref = new SoftReference<RefObj>(refObj, queue);

弱引用

ReferenceQueue<String> queue = new ReferenceQueue<Object>();
WeakReference<RefObj> ref = new WeakReference<RefObj>(refObj, queue);

虚引用

虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

ReferenceQueue<String> queue = new ReferenceQueue<String>();
PhantomReference<String> pr= new PhantomReference<String>(new String("hello"), queue);
 System.out.println(pr.get());

ReferenceQueue

这个队列的作用,是当GC将一些引用回收的时候,将*Reference对象放到这个队列里面,这样就可以通过poll方法获取到了。
也就是说,这个对象一开始是空的,每当系统GC回收掉WeakReference或者另外两种引用所指向的对象,就会将这个Reference对象放进这个Queue中。相当于告诉你:“我已经回收掉一些对象了,你看看需不需要做什么特殊的操作”。

总结:(1)WeakReference可以使用在一些observer list上,比如你的界面需要持续监听一个事情,需要严格的调用成对的方法,addListener,removeListener,但是保不齐什么时候忘记了,或者因为一些特殊的情况,导致没有调用remove,这样你这个界面整个对象都被listener list一直持有了,就相当于内存泄露了。这个时候,可以使用WeakReference,这样就算没有remove,gc也能干掉。
(2)SoftReference可能在实现一些缓存上会比较有用。
(3) ReferenceQueue相当于给我们一个能力去知道我的对象什么时候被回收了,那么如果需要做一些后续的事情,就可以依靠这个时机。

内存泄露检测工具

LeakCanary(可以使用它来检测Activity是否能够被GC及时回收)

原理:
(1)在Android中,当一个Activity走完onDestroy生命周期后,说明该页面已经被销毁了,应该被系统GC回收。
(2)通过Application.registerActivityLifecycleCallbacks()方法注册Activity生命周期的监听,每当一个Activity页面销毁时候,获取到这个Activity去检测这个Activity是否真的被系统GC。
(3)通过WeakReference + ReferenceQueue来判断对象是否被系统GC回收,WeakReference 创建时,可以传入一个 ReferenceQueue 对象。当被 WeakReference 引用的对象的生命周期结束,一旦被 GC 检查到,GC 将会把该对象添加到 ReferenceQueue 中,待ReferenceQueue处理。当 GC 过后对象一直不被加入 ReferenceQueue,它可能存在内存泄漏。

android自带工具Profiler

比如某个页面A可能存在内存泄露,测试时多次进入改页面并退出,退出页面后gc,然后点击“dump”按钮,用于生成HPROF文件,HPROF文件中的的数据是点击这个生成按钮这一瞬java VM执行时共享Heap中的数据,共享Heap中的数据主要包含了类的实例和数组对象,通常用于内存泄漏分析。
android开发--内存泄露
Allocations:堆中的分配数。(根据该值可以确定是否存在内存泄露,)
Native Size:此对象类型使用的原生内存总量(以字节为单位)。只有在使用 Android 7.0 及更高版本时,才会看到此列。您会在此处看到采用 Java 分配的某些对象的内存,因为 Android 对某些框架类(如 Bitmap)使用原生内存。
Shallow Size:此对象类型使用的 Java 内存总量(以字节为单位)。
Retained Size:为此类的所有实例而保留的内存总大小(以字节为单位)。

开发中常见的内存泄露问题

Android 5.1 Webview 内存泄漏

webview引起的内存泄漏主要是因为
org.chromium.android_webview.AwContents 类中注册了component callbacks,但是未正常反注册而导致的。

  @Override
    public void onAttachedToWindow() {
        if (isDestroyed()) return;
        if (mIsAttachedToWindow) {
            Log.w(TAG, "onAttachedToWindow called when already attached. Ignoring");
            return;
        }
		......
        mComponentCallbacks = new AwComponentCallbacks();
        mContext.registerComponentCallbacks(mComponentCallbacks);
    }
    

    @Override
    public void onDetachedFromWindow() {
        if (isDestroyed()) return;
        if (!mIsAttachedToWindow) {
            Log.w(TAG, "onDetachedFromWindow called when already detached. Ignoring");
            return;
        }
		......
        if (mComponentCallbacks != null) {
            mContext.unregisterComponentCallbacks(mComponentCallbacks);
            mComponentCallbacks = null;
        }
		......
    } 

看这段代码看不出来什么问题,onAttach的时候 register,detach的时候 unregister, 不会存在问题
原因在于,在Activity的destroy的时候,调用 webview的destroy,onDestroy 发生在 onDetach 之前,导致onDetachedFromWindow方法在if (isDestroyed()) return;直接返回了。

解决方法:

 protected void onDestroy() {
      if (mWebView != null) {
      //把onDetachedFromWindow方法提到 destroy()方法之前
          ((ViewGroup) mWebView.getParent()).removeView(mWebView);
          mWebView.destroy();
          mWebView = null;
      }
      super.onDestroy();
  }

Handler导致内存泄露

Activity中直接new Handler,会到导致内存泄露。

原因,内部类持有外部类的实例,所以Handler实例持有Activity,又因为Message的target持有Handler实例,message放在消息队列中,如果Activity结束的时候,消息还未执行,就会因为message间接持有activity的实例,导致activity内存泄露。

解决方法,(1)Handler弱引用Activity
(2)使用内部静态类 继承 Handler,这样Handler不会持有Activity
(3)handler.removeCallbacksAndMessages(null),删除Handler所有的消息和回调函数。

BroadcastReceiver记得调用unregisterReceiver()解除绑定

Activity中使用了registerReceiver()方法注册了一个BroadcastReceiver,如果没在Activity的生命周期内调用unregisterReceiver()方法取消注册此BroadcastReceiver,由于BroadcastReceiver不止被Activity引用,还可能会被AMS等系统服务、管理器等之类的引用,导致BroadcastReceiver无法被回收,而BroadcastReceiver中又持有着Activity的引用(即:onReceive方法中的参数Context),会导致Activity也无法被回收(虽然Activity回调了onDestroy方法,但并不意味着Activity被回收了),从而导致严重的内存泄漏。

输入、输出流,数据库操作等,结束时记得close

相关标签: 移动端