引用类型
引用包括强引用(Strong ref)、软引用(Soft ref)、弱引用(weak ref)、虚引用(幻象引用,PhantomReference)。不同的引用类型, 主要体现的是对象不同的可达性(reachable) 状态
和对垃圾收集的影响。
- 强引用 最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。
- 软引用 相对强引用弱化一些的引用,可以让对象豁免一些垃圾收集,只有当JVM认为内存不足时,才会去试图回收软引用指向的对象。JVM会确保在抛出OutOfMemoryError之前,清理软引用指向的对象。软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
- 弱引用 不能使对象豁免垃圾收集, 仅仅是提供一种访问在弱引用状态下对象的途径。维护一种非强制性的映射关系,如果试图获取时对象还在,就使用它,否则重现实例化。它同样是很多缓存实现的选择。
- 虚引用 不能通过它访问对象。幻象引用仅仅是提供了一种确保对象被finalize以后,做某些事情的机制,比如,通常用来做所谓的Post-Mortem清理机制,我在专栏上一讲中介绍的Java平台自身Cleaner机制等。
除了幻象引用(因为get永远返回null),如果对象还没有被销毁,都可以通过get方法获取原有对象。这意味着,利用软引用和弱引用,我们可以将访问到的对象,重新指向强引用,也就是人为的改变了对象的可达性状态!在对象执行finallize方法时也可以使用强引用来挽留对象。
引用队列(ReferenceQueue)
当gc(垃圾回收线程)准备回收一个对象时,如果发现它还仅有软引用(或弱引用,或虚引用)指向它,就会在回收该对象之前,把这个软引用对象(或弱引用,或虚引用,注意不是Reference对象的referent。)加入到与之关联的引用队列(ReferenceQueue)中。如果一个软引用(或弱引用,或虚引用)对象本身在引用队列中,就说明该引用对象所指向的对象被回收了。可以通过扩展软引用(或弱引用,或虚引用)来实现在Referent对象入队时,同时做一些清理相关对象的操作。
软引用、弱引用、虚引用对象本身是个强引用,不会自动被gc回收,需要手动进行调用poll或remove从队列里取出,当不再被引用时自动回收。WeakHahsMap 的实现原理简单来说就是HashMap里面的条目 Entry继承了 WeakReference,那么当 Entry 的 key 不再被使用(即,引用对象不可达)且被 GC 后,那么该 Entry 就会进入到 ReferenceQueue 中。当我们调用WeakHashMap 的get和put方法会有一个副作用,即清除无效key对应的Entry(WeakHashMap的Entry扩展了WeakReference类,同时保存了key的hashcode)。
TODO 使用引用队列检测内存泄露
Reachability Fence
按照Java语言规范,如果一个对象没有指向强引用,就符合垃圾收集的标准,有些时候,对象本身并没有强引用, 但是也许它的部分属性还在被使用,这样就导致诡异的问题,所以我们需要一个方法,在没有强引用情况下,通知JVM对象是在被使用的。
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);
}
}
}
方法action的执行,依赖于对象的部分属性,所以被特定保护了起来。否则,如果我们在代码中调用new Resource().action()
, 那么就可能会出现困扰,因为没有强引用指向我们创建出来的Resource对象,JVM对它进行finalize操作是完全合法的。类似的书写结构,在异步编程中似乎是很普遍的,因为异步编程中往往不会用传统的“执行->返回->使用”的结构。