java weak references

时间:2019-09-29
本文章向大家介绍java weak references,主要包括java weak references使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

java四种引用类型

在《深入理解Java虚拟机》中的简单描述:

  1. 强引用(StrongReference):就是在代码中普遍存在的,类似Object object = new Object()这类的引用,只要强引用还存在,GC永远不会回收掉被引用的对象。
  2. 软引用(SoftReference):用来描述一些还有用但非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常时,将会把这些对象列入回收范围之中进行回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
  3. 弱引用(WeakReference):也是用来描述非必须对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到了下一次GC发生之前。当GC工作时,无论当时内存是否足够,都会回收只被弱引用关联的对象。
  4. 虚引用(PhantomReference):虚引用也称幽灵引用或者幻影引用,它是最弱的一种引用关系,也无法通过虚引用来取得一个对象实例,为一个对象设置虚引用的唯一目的就是在这个对象被GC回收是收到一个系统通知。

对象在内存中的状态

java对象在堆内存有向图中的状态分成3种:

  1. 可达状态:当一个对象被创建后,有一个以上的引用指向它,在有向图中可以从起始顶点导航到该对象,那么它就处于可达状态。
  2. 可恢复状态:对于一个处于可达状态的对象,如果不再有任何引用指向它,在有向图中从起始顶点不能导航到该对象,那么它进入可恢复状态。在这个状态下,JVM的垃圾回收器准备回收该对象占用的内存。但在回收该对象之前,JVM会调用该对象的finalize()进行资源清理。如果在调用finalize()期间重新让一个以上引用指向该对象,则该对象会再次进入可达状态;否则,该对象将进入不可达状态。
  3. 不可达状态:当一个对象进入不可达状态后,JVM的垃圾回收器才会真正回收该对象所占用的内存。

针对上面四种引用方式,对象还有以下这几个状态:

  1. 软可及状态:对象不是强可及的,但是可以从根节点开始通过一个或者多个未被清除的软引用对象触及。当垃圾收集器清除一个和引用队列有关联的软引用对象时,它把该软引用对象加入队列。
  2. 弱可及状态:对象即不是强可及的也是不是软可及的,但是从根节点开始可以通过一个或多个未被清除的弱引用对象时触及。垃圾收集器必须归还弱可及对象所占据的内存。在发生GC时,GC会清除所有到此弱可及对象的弱引用。当GC清除一个和引用队列有关联的弱引用对象时,它把该引用对象加入队列。
  3. 虚可及状态:对象不是强可及、软可及,也不是弱可及,并已经被断定不会被任何finalize()方法复活,如果它自己定义了终结方法,那它的finalize()方法之前也已经运行过一次了,并且它可以从根节点开始通过一个或者多个虚引用对象触及。一个虚引用的引用目标变为虚可及状态,GC会立即将此引用对象加入引用队列。GC从不会清除一个虚引用,即不会执行虚引用的Reference#clear()方法,所有的虚引用都必须由程序明确的清除。

四种引用类型比较

引用类型 取得目标对象方式 垃圾回收条件 是否可能内存泄漏
强引用 直接调用 不回收 可能
软引用 通过 get() 方法 视内存情况回收 不可能
弱引用 通过 get() 方法 永远回收 不可能
虚引用 无法取得 不回收 可能

Strong references

public class {
public static void main(String[] args) {
Object object = new Object() {
public String toString() {
return "Test StrongReference Object";
}
protected void finalize() {
System.out.println("finalize : " + this);
}
};
// 对象只要是可达的,就算内存溢出也不会被GC回收
System.gc(); // 无效
}
}

FinalReference 以及 Finalizer

FinalReference作为java.lang.ref里的一个不能被公开访问的类,又起到了一个什么样的作用呢?作为他的子类,Finalizer又在垃圾回收机制里扮演了怎么样的角色呢?

实际上,FinalReference代表的正是Java中的强引用,如这样的代码 :

StringBuffer buffer = new StringBuffer();

在虚拟机的实现过程中,实际采用了FinalReference类对其进行引用。而Finalizer除了作为一个实现类外,更是在虚拟机中实现一个FinalizerThread,以使虚拟机能够在所有的强引用被解除后实现内存清理。

显式GC回收不可达对象

public class ExplicitGarbageRetrieve {
public static void main(String[] args) throws InterruptedException {
Object object = new Object() {
public String toString() {
return "Test Object";
}
protected void finalize() {
System.out.println("finalize : " + this);
}
};
object = null;
System.gc();
TimeUnit.SECONDS.sleep(1);
}
}

运行程序输出如下:

finalize : Test Object

GC的过程中,当一个强引用被释放,由系统垃圾收集器标记后的对象,会被加入Finalizer对象中的ReferenceQueue中去,并调用Finalizer.runFinalizer()来执行对象的finalize()方法。

Soft references

SoftReference javadoc

Soft reference objects, which are cleared at the discretion of the garbage collector in response to memory demand. Soft references are most often used to implement memory-sensitive caches.

Suppose that the garbage collector determines at a certain point in time that an object is softly reachable. At that time it may choose to clear atomically all soft references to that object and all soft references to any other softly-reachable objects from which that object is reachable through a chain of strong references. At the same time or at some later time it will enqueue those newly-cleared soft references that are registered with reference queues.

All soft references to softly-reachable objects are guaranteed to have been cleared before the virtual machine throws an OutOfMemoryError. Otherwise no constraints are placed upon the time at which a soft reference will be cleared or the order in which a set of such references to different objects will be cleared. Virtual machine implementations are, however, encouraged to bias against clearing recently-created or recently-used soft references.

Direct instances of this class may be used to implement simple caches; this class or derived subclasses may also be used in larger data structures to implement more sophisticated caches. As long as the referent of a soft reference is strongly reachable, that is, is actually in use, the soft reference will not be cleared. Thus a sophisticated cache can, for example, prevent its most recently used entries from being discarded by keeping strong referents to those entries, leaving the remaining entries to be discarded at the discretion of the garbage collector.

当JVM中的对象没有强引用,但是还有软引用时,则表示此对象对GC而言为软可及对象。

软引用是主要用于内存敏感的高速缓存。在JVM报告内存不足之前会清除所有的软引用,这样以来GC就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于GC的算法和GC运行时可用内存的大小。

Avoid Soft References for Caching

In practice, soft references are inefficient for caching. The runtime doesn't have enough information on which references to clear and which to keep. Most fatally, it doesn't know what to do when given the choice between clearing a soft reference and growing the heap.

The lack of information on the value to your application of each reference limits the usefulness of soft references. References that are cleared too early cause unnecessary work; those that are cleared too late waste memory.

下面测试代码是在JVM的新开的线程中,使用一个map对象将内存耗尽,从而触发GC,回收软可及对象。

public class ReferenceTestUtil {
/**
* 消耗大量内存
*/
public static void drainMemory() {
new Thread() {
public void run() {
Map<String, String> map = new HashMap<String, String>();
for (int i = 0; ; i++) {
map.put("key: " + i, "value: " + i);
}
}
}.start();
}
}

SoftReference 测试程序

public class SoftReferenceTest {
private static final Logger LOGGER = LoggerFactory.getLogger(SoftReferenceTest.class);
public static void main(String[] args) throws InterruptedException {
Object object = new Object() {
@Override
public String toString() {
return "Test SoftReferenced Object";
}
@Override
protected void finalize() {
LOGGER.info("finalize {}", this);
}
};
SoftReference reference = new SoftReference(object);
// 只要内存还足够,soft reference指向的对象并不会被GC回收object对象
// 只有在内存溢出前,内存不够时,才会触发GC回收这这些软可及对象
System.gc();
TimeUnit.SECONDS.sleep(1); // GC不会回收
LOGGER.info("object is : {}", reference.get());
LOGGER.info("begin drain memory and trigger GC");
// 耗尽内存以触发GC
ReferenceTestUtil.drainMemory();
}
}

运行程序输出如下:

[ main] object is : Test SoftReferenced Object

[ main] begin drain memory and trigger GC

[ Finalizer] finalize Test SoftReferenced Object

从上述运行可以看到:第一次GC之后,并不会回收软可及对象,接下来在另一个线程中内存被耗尽,GC再次触发,并回收软可及对象。

软可及对象在GC时并不一定会被回收,具体要根据实际内存使用情况决定。当GC决定要回收软可及对象时,执行以下过程:

  1. 首先将object的referent设置为null,不再引用heap中的object对象。
  2. 将heap中的object对象设置为可结束的finalizable
  3. 运行object对象的finalize()方法。
  4. 最后软引用的引用目标会被GC直接clear掉,object进入不可及状态,并添加软引用到其ReferenceQueue中,释放对象占用的内存空间。

Weak references

WeakReference javadoc

Weak reference objects, which do not prevent their referents from being made finalizable, finalized, and then reclaimed. Weak references are most often used to implement canonicalizing mappings.

Suppose that the garbage collector determines at a certain point in time that an object is weakly reachable. At that time it will atomically clear all weak references to that object and all weak references to any other weakly-reachable objects from which that object is reachable through a chain of strong and soft references. At the same time it will declare all of the formerly weakly-reachable objects to be finalizable. At the same time or at some later time it will enqueue those newly-cleared weak references that are registered with reference queues.

当JVM中的对象没有强引用、软引用,只有弱引用时,则表示此对象对GC而言为软可及对象。

由于JVM总会回收弱引用指向的对象,所以弱引用不会造成内存泄漏。

ReferenceQueue 监听线程

以下程序用以监听对象在执行finalize()之后,被添加到ReferenceQueue时,输出不同引用类型的信息,也可以从这个线程中看到,WeakReference指向的对象被添加到引用队列时,referent对象已经被置为null了,即弱引用的引用目标已经被GC直接clear了,这里GC并不是调用Reference#clear()方法,是JVM底层直接清理的,而PhantomReference指向的对象被添加到引用队列时,referent对象并没有被置为null,仍然可以访问到Heap中的这个对象。

public class ReferenceQueueMonitor implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(ReferenceQueueMonitor.class);
private static volatile boolean isRun = true;
private ReferenceQueue<Reference> queue;
public ReferenceQueueMonitor(ReferenceQueue<Reference> queue) {
this.queue = queue;
}
@Override
public void run() {
while (isRun) {
Reference reference = queue.poll();
if (reference != null) {
try {
LOGGER.info("reference is : {}", reference);
Field referent = Reference.class.getDeclaredField("referent");
referent.setAccessible(true);
Object result = referent.get(reference);
if (result == null) {
LOGGER.info("object enqueued and removed from heap ...");
} else {
LOGGER.info("object enqueued and object is still in heap : {}", result);
LOGGER.info("GC will collect: {}@{}", result.getClass(), result.hashCode());
if (reference instanceof PhantomReference) {
LOGGER.info("clear reference of phantom manually.");
reference.clear();
}
}
isRun = false;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}

GC收集弱可及对象的执行过程和软可及一样,只是GC不会根据内存情况来决定是不是收集该对象,而是立即回收。

  1. 首先将object的referent设置为null,不再引用heap中的object对象。
  2. 将heap中的object对象设置为可结束的finalizable
  3. 运行object对象的finalize()方法。
  4. 最后弱引用的引用目标被GC直接clear掉,object进入不可及状态,并添加弱引用到其ReferenceQueue中,释放对象占用的内存空间。
public class WeakReferenceTest {
private static final Logger LOGGER = LoggerFactory.getLogger(WeakReferenceTest.class);
public static void main(String[] args) throws Exception {
Object object = new Object() {
@Override
public String toString() {
return "Referenced Object In WeakReference Test";
}
@Override
protected void finalize() {
LOGGER.info("finalize {}", this);
}
};
LOGGER.info("object is : {}@{}", object.getClass(), object.hashCode());
final ReferenceQueue queue = new ReferenceQueue<Object>();
ReferenceQueueMonitor monitor = new ReferenceQueueMonitor(queue);
new Thread(monitor).start();
Reference<Object> reference = new WeakReference<Object>(object, queue);
LOGGER.info("reference.get() is {}", reference.get());
object = null;
for (int i = 1; i < 3; i++) {
System.gc();
// 第1次GC发生时,就触发了对象的 finalize() 方法
// 并且对象直接从heap上被删除掉,并加入weak refrence引用队列
TimeUnit.SECONDS.sleep(1); // 等待GC线程运行1秒钟
LOGGER.info("GC {} END ...........", i);
}
}
}

上述程序中在运行GC时,都做了1秒钟的延时,因为JVM的垃圾回收是在GC的线程中运行的。程序运行后输出如下,注意其中的Finalizer线程的输出:

[ main] object is : class com.example.test.lang.ref.WeakReferenceTest$1@1711574013

[ main] reference.get() is Referenced Object In WeakReference Test

[ Thread-0] object enqueued and removed from heap ...

[ Finalizer] finalize Referenced Object In WeakReference Test

[ main] GC 1 END ...........

[ main] GC 2 END ...........

上述Thread-0线程中的输出,可以看出,弱可及对象在加入引用队列之后,弱引用的referent被设置为null。如果在对象的finalize()方法中不再有强引用复活此object,就会被GC回收内存。

Phantom references

PhantomReference javadoc

Phantom reference objects, which are enqueued after the collector determines that their referents may otherwise be reclaimed.

Phantom references are most often used for scheduling pre-mortem cleanup actions in a more flexible way than is possible with the Java finalization mechanism.

If the garbage collector determines at a certain point in time that the referent of a phantom reference is phantom reachable, then at that time or at some later time it will enqueue the reference.

In order to ensure that a reclaimable object remains so, the referent of a phantom reference may not be retrieved:

The get method of a phantom reference always returns null.

Unlike soft and weak references, phantom references are not automatically cleared by the garbage collector as they are enqueued. An object that is reachable via phantom references will remain so until all such references are cleared or themselves become unreachable.

如果GC发现某个虚引用指向的对象已经没有其他引用时,即为虚可及对象,并在之后会将虚引用加入引用队列。

虚引用会把引用指向的对象写进referent,但是get()方法返回结果永远为null

PhantomReference 测试程序一

public class PhantomReferenceTest {
private static final Logger LOGGER = LoggerFactory.getLogger(PhantomReferenceTest.class);
public static void main(String[] args) throws Exception {
Object object = new Object() {
@Override
public String toString() {
return "Referenced Object In PhantomReference Test";
}
@Override
protected void finalize() throws Throwable {
LOGGER.info("finalize {}", this);
}
};
LOGGER.info("object is : {}@{}", object.getClass(), object.hashCode());
final ReferenceQueue queue = new ReferenceQueue<String>();
ReferenceQueueMonitor monitor = new ReferenceQueueMonitor(queue);
new Thread(monitor).start();
Reference<Object> reference = new PhantomReference<Object>(object, queue);
LOGGER.info("reference.get() is {}", reference.get());
object = null;
for (int i = 1; i < 3; i++) {
System.gc();
// 第1次GC发生时,就触发了对象的 finalize() 方法
// 但是对象仍然存在于heap上,下一次GC才会被回收
TimeUnit.SECONDS.sleep(1); // 等待GC线程运行1秒钟
LOGGER.info("GC {} END ...........", i);
}
}
}

运行以上程序,输出结果如下:

[ main] object is : class com.example.test.lang.ref.PhantomReferenceTest$1@1711574013

[ main] reference.get() is null

[ Finalizer] finalize Referenced Object In PhantomReference Test

[ main] GC 1 END ...........

[ Thread-0] object enqueued and object is still in heap : Referenced Object In PhantomReference Test

[ Thread-0] GC will collect: class com.example.test.lang.ref.PhantomReferenceTest$1@1711574013

[ main] GC 2 END ...........

从以上输出可以看到:

  1. PhantomReferenceget()方法永远返回的是null,即虚引用不能获得实际对象。
  2. 因为object对象覆写了Object#finalize()方法,第1次GC时并不会直接回收对象,标记对象为finalizable,并且在Finalizer线程执行finalize()方法。
  3. 第2次GC时,将对象加入到引用队列中,并且等待GC回收object对象。

从上面的执行结果可以看到,PhantomReferenceWeakReference最大的区别主要有以下几点:

  1. 虚引用指向的对象不能通过reference.get()方法获取。
  2. PhantomReference中的referent对象不会被置为null,在引用队列中仍然可以访问到虚引用指向的对象,所以虚引用仍然会造成内存溢出,而弱引用是肯定要被GC回收的,因而不会产生内存溢出。
  3. GC不会直接将PhantomReference对象的引用目标clear掉,但是GC会不用调用Reference.clear()方法而直接将软引用和弱引用的引用目标clear掉。

PhantomReference 测试程序二

如上例所示,一个覆写了finalize()方法的对象如果想要被回收掉,需要经历二个单独的垃圾回收周期。

如果上述示例中的object对象没有覆盖finalize()方法,则1次GC之后对象就被回收。

将上述程序中的finalize()中的代码注释掉,如下:

public class PhantomReferenceTest {
private static final Logger LOGGER = LoggerFactory.getLogger(PhantomReferenceTest.class);
public static void main(String[] args) throws Exception {
Object object = new Object() {
@Override
public String toString() {
return "Referenced Object In PhantomReference Test";
}
@Override
protected void finalize() throws Throwable {
// LOGGER.info("finalize {}", this);
}
};
LOGGER.info("object is : {}@{}", object.getClass(), object.hashCode());
final ReferenceQueue queue = new ReferenceQueue<String>();
ReferenceQueueMonitor monitor = new ReferenceQueueMonitor(queue);
new Thread(monitor).start();
Reference<Object> reference = new PhantomReference<Object>(object, queue);
LOGGER.info("reference.get() is {}", reference.get());
object = null;
for (int i = 1; i < 3; i++) {
System.gc();
// 第1次GC发生时,就触发了对象的 finalize() 方法
// 但是对象仍然存在于heap上,下一次GC才会被回收
TimeUnit.SECONDS.sleep(1); // 等待GC线程运行1秒钟
LOGGER.info("GC {} END ...........", i);
}
}
}

运行程序输出如下:

[ main] object is : class com.example.test.lang.ref.PhantomReferenceTest$1@1711574013

[ main] reference.get() is null

[ Thread-0] object enqueued and object is still in heap : Referenced Object In PhantomReference Test

[ Thread-0] GC will collect: class com.example.test.lang.ref.PhantomReferenceTest$1@1711574013

[ main] GC 1 END ...........

[ main] GC 2 END ...........

从上述结果可以看到在一个GC周期内,虚引用指向的对象就被GC回收了,所以finalize()方法不建议被重写,去掉finalize()方法可以虚拟机GC变得更加简单。

References

  1. Understanding Weak References Blog
  2. 理解Java中的弱引用
  3. Java中三个引用类SoftReference 、 WeakReference 和 PhantomReference的区别
  4. 深入探讨 java.lang.ref 包
  5. 内存回收
  6. Garbage Collection

原文:大专栏  java weak references


原文地址:https://www.cnblogs.com/petewell/p/11607371.html