android开发--内存泄露
内存泄露
内存泄露
在Android中GC采用了标注和清理(Mark and
sweep)回收算法:从” GC Roots“集合开始,将内存中存活的对象整个遍历一遍,凡是能够被GC Roots直接或者间接引用到的对象保存起来,而剩下的孤魂野鬼的对象便被当成垃圾回收掉了。下面我画几个图展示一下GC的回收过程:
黄色圆形表示遍历后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中的数据主要包含了类的实例和数组对象,通常用于内存泄漏分析。
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被回收了),从而导致严重的内存泄漏。