Java中的四种引用和引用队列

强引用

正常的引用,生命周期最长,例如 Object obj = new Object(); 当JVM内存不足时,宁可抛出OutOfMemoryError,也不愿回收存活着强引用的对象

对象还活着吗?当一个普通对象没有其他引用关系,只要超过了引用的作用域或者显示的将引用赋值为null时,你的对象就表明不是存活着,这样就会可以被GC回收了。

软引用

生命周期比强引用短,通过SoftReference类实现,可以通过get方法获取对象。
A

当JVM内存不足时会先回收软引用指向的对象,即抛出OutOfMemoryError之前,会去清理软引用对象。

软引用通常会在最后一次引用后,还能保持一段时间,默认值是根据堆剩余空间计算的。

如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用 队列中。软引用可以和引用队列(referenceQueue)联用,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。

应用场景:软引用一般用来实现内存敏感的缓存,如果有空闲内存就可以保留缓存,当内存不足时就清理掉,这样就保证使用缓存的同时不会耗尽内存,如图片缓存框架中缓存图片就是通过软引用实现。

弱引用

弱引用是通过WeakReference类实现的,它的生命周期比软引用还要短,也是通过get()方法获取对象。

JVM内存回收时,不论是否内存不足,都会回收弱引用的对象。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象。

弱引用可以配合引用队列,如果弱引用所引用的对象被垃圾 回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

应用场景:弱引用适用于内存敏感的缓存,如ThreadLocal中的key就用到了弱引用。

幻象引用

通过PhantomReference类实现,任何时候都可以被回收,可以看作没有引用。必须和引用队列联用。无法通过get方法获取对象。

幻象引用仅仅是提供了一种确保对象被 fnalize 以后,做某些事情的机制。当垃圾回收器准备回收一个对象时,如 果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之 前采取一些程序行动。

应用场景:可用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾收集器回收之前会收到一条系统通知。

Object counter = new Object();
ReferenceQueue refQueue = new ReferenceQueue<>();
PhantomReference<Object> p = new PhantomReference<>(counter, refQueue);
counter = null;
Sysem.gc();
try {
 // Remove是一个阻塞方法,可以指定timeout,或者选择一直阻塞
 Reference<Object> ref = refQueue.remove(1000L);
 if (ref != null) {
 // do something
 }
} catch (InterruptedException e) {
 // Handle it
}

Reachability Fence

按照Java语言规范,如果一个对象没有指向强引用,就符合垃圾收集的标准。但有的场景中,对象本身并没有强引用,但是也许它的部分属性还在被使用。为了防止对象被回收,我们需要一个方法,在没有强引用情况下,通知JVM对象是在被使用的。

Java9之后,java.lang.ref.Reference给我们提供了一种方法, reachabilityFence,来声明某些没有强引用的对象,依然强可达。编程时,可以将需要reachability保障的代码段利用try-fnally包围起来,在finally里明确声明对象强可达

class Resource {
 private satic ExternalResource[] externalResourceArray = ...
 int myIndex; Resource(...) {
 myIndex = ...
 externalResourceArray[myIndex] = ...;
 ...
 }
 protected void fnalize() {
 externalResourceArray[myIndex] = null;
 ...
 }
 public void action() {
 try {
 // 需要被保护的代码
 int i = myIndex;
 Resource.update(externalResourceArray[i]);
 } fnally {
 // 调用reachbilityFence,明确保障对象srongly reachable
 Reference.reachabilityFence(this);
 }
 }
 private static void update(ExternalResource ext) {
 ext.status = ...;
 }
}

//非强引用
new Resource().action()

在JDK源码中,reachabilityFence大多使用在Executors或者类似新的HTTP/2客户端代码中,大部分都是异步调用的情况。

参考

《深入理解Java虚拟机》周志明
《Java核心技术36讲》杨晓峰
https://baijiahao.baidu.com/s?id=1629253892215446066&wfr=spider&for=pc

posted @ 2020-03-10 10:59  chzhyang  阅读(3232)  评论(0编辑  收藏  举报