Fork me on GitHub

强引用、弱引用、软引用、虚引用

首先还是从问题入手:这个四个引用有什么区别,具体使用场景是什么?

在Java中,除了基本数据类型的变量(int ,double等),其他都是引用类型,指向不同的对象(类、接口或者数组等复杂的数据结构)。在这里拓展一下,浅拷贝和深拷贝的区别就是在clone对象时,是否clone引用类型的数据变量(记个小笔记)。


其实不同的引用类型的具体作用体现在JVM中,主要影响对象的可达性和GC回收。

强引用:

常见的普通引用,像Class c =new Class();c就是一个强引用指向Class对象。只要还有强引用指向对象,那就表明对象还活着,当JVM内存不足时,JVM宁愿OOM,使程序终止,也不会回收具有强引用的对象;相反对于一个普通对象,如果没有其他的引用指向该对象,只要超过了引用的作用域(比如作用域为方法级或者块级,程序运行完了方法后)或者显式地将强引用赋值为null,GC就会回收该对象。

软引用:

相对强引用弱化一些的引用,可以让对象避免一些垃圾回收。只有当JVM判定内存不足时,才会去回收软引用指向的对象:即JVM确保在发生OOM之前,会去清理软引用对象。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象
软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。(例如图片缓存)

弱引用:

弱引用并不能是对象避免垃圾收集,仅仅提供了一种访问对象的途径。这就可以用来构建一种没有特定约束的关系,如果试图获取的对象还在,就使用它,否则重新实例化。弱引用同样可以和一个引用队列联合使用。
弱引用同样是很多缓存实现的选择。

虚引用:

虚引用有的地方也翻译成幻象引用。不能通过它去访问对象,虚引用仅仅提供了一种确保对象被finalize后,做某些事情的机制。比如Post-Mortem 清理机制。当为对象设置虚引用时,该对象被GC回收时,会收到一个系统通知,从而来跟踪对象的GC回收情况。


延伸一波:

(1). 所有的引用类型都是继承自抽象类 java.lang.ref.Reference;看看里面的get方法

1
2
3
4
5
6
7
8
/**
* Returns this reference object's referent. If this reference object has
* been cleared, either by the program or by the garbage collector, then
* this method returns <code>null</code>.
*/
public T get() {
return this.referent;
}

可以看到,如果对象没有被销毁,就可以通过get方法获取到原有对象当然虚引用除外(因为它的get返回null),所以我们可以将通过软引用和弱引用访问到的对象重新指向强引用,也就是认为的改变对象的可达性状态。因此对于软引用、弱引用之类,垃圾收集器可能会存在二次确认的问题,以保证处于弱引用状态的对象,没有改变为强引用。

(2). 引用队列(ReferenceQueue)
我们在创建各种引用并关联到响应对象时,可以选择是否需要关联引用队列,JVM 会在特定时机将引用 enqueue 到队列里,我们可以从队列里获取引用进行相关后续逻辑。尤其是幻象引用,get 方法只返回 null,如果再不指定引用队列,基本就没有意义了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Object object = new Object();
ReferenceQueue refQueue = new ReferenceQueue<>();
PhantomReference<Object> p = new PhantomReference<>(object, refQueue);
object = null;
System.gc();
try {
// Remove 是一个阻塞方法,可以指定 timeout,或者选择一直阻塞
Reference<Object> ref = refQueue.remove(1000L);
if (ref != null) {
// do something
}
} catch (InterruptedException e) {
// Handle it
}

gc准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。利用引用队列,我们可以在对象处于相应状态时(对于幻象引用,就是前面说的被 finalize 了,处于幻象可达状态),执行后期处理逻辑。

(3). 拓展一种特殊情况:通常情况下,如果第一个对象没有指向强引用就符合GC回收标准,但是有时候对象本身并没有强引用,但是它的部分属性还在被使用。所以需要一个方法,在没有强引用情况下,通知JVM对象是在被使用的。在JAVA 9中可以按照下面代码来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Resource {
private static ExternalResource[] externalResourceArray = ...
int myIndex;
//构造
Resource(...) {
myIndex = ...
externalResourceArray[myIndex] = ...;
...
}
protected void finalize() {
externalResourceArray[myIndex] = null;
...
}
public void action() {
try {
// 需要被保护的代码
int i = myIndex;
Resource.update(externalResourceArray[i]);
} finally {
// 调用 reachbilityFence,明确保障对象 strongly reachable
Reference.reachabilityFence(this);
}
}
private static void update(ExternalResource ext) {
ext.status = ...;
}
}

action()执行依赖对象的部分属性,假如我们直接

1
new Resource().action()//这种写法在异步编程中较为常见

因为没有强引用指向Resource对象,所以JVM对它进行finalize是合法的,因此需要利用reachbilityFence方法保护该对象,声明该对象为强可达。


android中具体的应用:利用软、弱引用防止内存泄漏

常用的比如handler、AsyncTask等。
改进的handler写法:(利用静态内部类和弱引用防止内存泄漏)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.sendMessageDelayed(mMesssage, 1000 * 60 * 10);
finish();
}
private static class MyHandler extends Handler {
private final WeakReference<MainActivity> mActivity;
public MyHandler(MainActivity activity) {
mActivity = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = mActivity.get();
if (activity != null) {
// do something
}
}
}
private final MyHandler mHandler = new MyHandler(this);
}