四大引用
Java提供了四种级别的应用类型:强引用、软引用、弱引用及虚引用。那么这四种引用类型分别有什么作用,又有什么区别呢?
强引用(StrongReference)
强引用是我们最常用的一种引用类型。当我们使用new
关键字去新建一个对象的时候,创建的就是强引用。比如:
Obejct object = new Obejct();
强引用有如下特点:
- 只要强引用存在,垃圾收集器永远不会回收掉被引用的对象。
- 强引用可能会导致内存泄漏
- JVM宁愿抛出OOM异常,也不会回收强引用。
强引用与Android开发中的OOM异常
OOM异常是Android开发过程中很常见的一种异常,下面来看一个例子:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new MyAsyncTask(this).execute();
}
//非静态内部类,在MyAsyncTask的整个生命周期内都会持有对外部类MainActivity的强引用
private class MyAsyncTask extends AsyncTask {
MainActivity mActivity;
public MyAsyncTask(MainActivity mainActivity) {
mActivity=mainActivity;
}
@Override
protected Object doInBackground(Object[] params) {
// 模拟耗时任务
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return doSomething();
}
private Object doSomething() {
return new Object();
}
@Override
protected void onPostExecute(Object object) {
super.onPostExecute(object);
// 假如在下方有更新UI的代码....
}
}
}
简单的解释一下上面这段代码:MyAsyncTask会跟随Activity的onCreate去创建并开始执行一个长时间的耗时任务,并在耗时任务完成后去更新MainActivity中的UI。这是一个很常见的使用场景,却会导致内存泄露问题:
但是,在Java中,非静态内部类会在其整个生命周期中持有对它外部类的强引用。
所以,当MainActivity被销毁时,MyAsyncTask中的耗时任务可能仍没有执行完成,MyAsyncTask则会一直存活。此时,由于MyAsyncTask持有着其外部类,即MainActivity的引用,将导致MainActivity不能被垃圾回收,这就会导致内存泄漏。如果MainActivity中还持有着Bitmap等大对象,反复进出这个页面几次可能就会出现OOM了。
那么我们如何避免这样的问题出现呢?接下来就该弱引用出场了。
弱引用(WeakReference )
- 被弱引用关联的对象只能生产到下一次垃圾收集发生之前。当垃圾收集器工作室,无论内存是否足够,都会回收弱引用关联的对象。
- 可以使用WeakReference类来实现弱引用
使用弱引用避免OOM
接下来,我们看看如何使用弱引用改造上面的例子:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new MyAsyncTask(this).execute();
}
//MyAsyncTask改为了静态内部类
private static class MyAsyncTask extends AsyncTask {
//对外部类MainActivity的引用换成弱引用
private WeakReference<MainActivity> mActivity;
public MyAsyncTask(MainActivity mainActivity) {
mActivity= new WeakReference<>(mainActivity);
}
@Override
protected Object doInBackground(Object[] params) {
// 模拟耗时任务
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return doSomething();
}
private Object doSomething() {
return new Object();
}
@Override
protected void onPostExecute(Object object) {
super.onPostExecute(object);
//判空
if (mActivity.get()!=null){
// 假如在下方有更新UI的代码....
}
}
}
}
从代码中可以看出,我们把MyAsyncTask改为了静态内部类,并且其对外部类MainActivity的引用换成了弱引用。
修改之后,当MainActivity destroy的时候,由于MyAsyncTask是通过弱引用的方式持有MainActivity,所以并不会阻止MainActivity被垃圾回收器回收,也就不会有内存泄露产生了。
弱引用的其他应用
-
内存泄漏监测框架LeakCanary
弱引用与ReferenceQueue联合使用。ReferenceQueue队列的具体作用就是当发生 GC 后,弱引用所持有的对象如果被回收就会进入该队列,所以只要在 activity onDestory 时,把 Activity 对象绑定在 WeakReference 中,然后手动执行一次 GC,然后观察 ReferenceQuery 中是不是包含对应的 Activity 对象,如果不包含,说明 Activity 被强引用,也就是发生了内存泄漏。
WeakHashMap
当key只有弱引用时,GC发现后会自动清理键和值,作为简单的缓存表解决方案。ThreadLocal
ThreadLocal.ThreadLocalMap.Entry 继承了弱引用,key为当前线程实例,和WeakHashMap基本相同。
软引用(SoftReference)
对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围并进行二次回收。
弱引用与软引用的根本区别在于:只具有弱引用的对象拥有更短暂的生命周期,可能随时被回收。而只具有软引用的对象只有当内存不够的时候才被回收,在内存足够的时候,通常不被回收。
从引用的强度来讲: 强引用 > 软引用 > 弱引用。
-
简单的使用例子:
//软引用的简单使用 News news = new News(); SoftReference softReference = new SoftReference(news); News news = (News) softReference.get();
软引用的应用
- 从网络上获取图片,可以通过软引用缓存起来。但是,在实践中,使用软引用作为缓存时效率是比较低的,系统并不知道哪些软引用指向的对象应该被回收,哪些应该被保留。过早被回收的对象会导致不必要的工作,比如Bitmap要重新从SdCard或者网络上加载到内存。在Android开发中,一种更好的选择是使用LruCache。
虚引用(PhantomReference)
- 也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。
- 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。
- 可以使用PhantomReference类来实现虚引用。
- 应用:
- 为一个对象设置虚引用关联的唯一目的就是希望能在这个对象被收集器回收时收到一个系统通知。