图片引用自https://blog.csdn.net/u011801189/article/details/52723868
根据Reference这个类里面的注释,整个Reference的继承体系跟GC是紧密联系在一起的。当Reference对象被GC处理过,说明这个Reference所指向的对象已经被回收过了,那么这个Reference对象也应该做相应的处理;如果该Reference对象注册了ReferenceQueue,那么它就会被丢到相应的ReferenceQueue中做回收处理。
从上图注意到,当Reference对象在Reference.pending队列中时,它们是用discovered
这个成员变量来完成队列的连接的;
这个Reference对象是active的状态,说明这个Reference所指向的对象还没有成为垃圾,还没有被GC过。这个Reference对象还没有进入Reference的世界,它还在GC的监控下呢;当被垃圾回收后,也就是进入Reference的世界后,这个discovered
域被复用作链表的连接,此时并不适用next指针,只是把next指向this。
当Reference对象真正被放入它所注册的ReferenceQueue里时,next指针才被使用,用来连接队列;此时discovered
域被置为null。
l . 软引用,一般使用于一些内存敏感性应用,做cache使用。被软引用的对象,在JVM抛出OutOfMemoryError之前,将会被回收掉。它的这个特性使得它做为进程内部的cache最为合适。
l 弱引用,一般用于保存一些跟对象生命周期相同的一些信息,一旦这些对象被垃圾回收,我们就可以对这些对象进行回收。这个是怎么做到的呢?基本上还是由于Reference所提供的功能有关。去看Reference的构造函数你会发现,它会要求传入一个ReferenceQueue,而这个Queue就是为了提供一种机制,这种机制允许程序员在对象被垃圾回收之前被通知到,而做出一些业务逻辑。而这种机制则弥补了JVM的finalizer机制的不足。因为JVM的finalizer机制并不保证finalizer一定会被调用到,这样当我们希望一个对象生命周期结束后它的相关资源也被释放的逻辑就不能够被保证。而且据说(为证实),finalizer是通过JNI实现,通过JVM调用开销也比较大。而JVM通过提供这种Reference机制,从而为程序员提供了处理资源回收的另外一种方法。
l 虚引用,也是可以提供这种对象被垃圾回收时的通知机制。只不过跟上面两个的区别是,PhantomReference的get方法被实现了一直返回null。这就意味着,当你通过ReferenceQueue拿到PhantomReference的通知时,你是不能再对它做出一个强引用的,你只能更具需要做出自己的cleanup的逻辑。这也说明了PhantomReference是pre-mortem的,也就是说实在真正finalize之前的。如果通过get方法可以获得对象本身,而后又对其建立一个强引用,则在后面的垃圾回收处理时,就会出错。所以干脆这里不让程序员获得对象的引用。而SoftReference和WeakReference应该就是在JVM的finalize机制之后的。
今天吃饭的时候想到一个问题,就是对于PhantomReference
,它的get
方法是返回的null的,如果是这样,我通过ReferenceQueue
的方式即使能拿到这个PhantomReference
,也还是不能对这个对象做实质的资源回收啊。那到底要怎么做才能回收与它的referent
想关联的资源呢?
publicclassLargeObjectFinalizerextendsPhantomReference<Object> {
publicLargeObjectFinalizer(
Object referent, ReferenceQueue<? super Object> q) {
super(referent, q);
}
publicvoidfinalizeResources() {
// free resources
System.out.println("clearing ...");
}
}
然后,在利用这个LargeObjectFinalizer
关联到我们的referent
ReferenceQueue<Object>referenceQueue = new ReferenceQueue<>();
List<LargeObjectFinalizer>references = new ArrayList<>();
List<Object>largeObjects = new ArrayList<>();
for (int i = 0; i < 10; ++i) {
Object largeObject = new Object();
largeObjects.add(largeObject);
references.add(new LargeObjectFinalizer(largeObject,referenceQueue));
}
largeObjects= null;
System.gc();
Reference<?>referenceFromQueue;
for(PhantomReference<Object> reference : references) {
System.out.println(reference.isEnqueued());
}
while((referenceFromQueue = referenceQueue.poll()) != null) {
((LargeObjectFinalizer)referenceFromQueue).finalizeResources();
referenceFromQueue.clear();
}
上面的这个例子很简单,但是足够可以说明问题,当垃圾回收工作时,会把我们的LargeObjectFinalizer
放到我们提供的referenceQueue
上,然后,我们从它上面取下我们的LargeObjectFinalizer
,调用资源回收方法finalizeResources
。
我觉得这个算是使用PhantomReference
做垃圾回收的一个典型例子了。我们并不是直接使用PhantomReference
,而是通过扩展它,将我们要关注的对象,比如这里的Object
,以及跟该对象相关联的一些资源,关联到我们的LargeObjectFinalizer
中,当然这是需要修改它的构造函数,或者通过其它的一些方式关联到这个LargeObjectFinalizer
里面的。只有这样,我们才能通过这个ReferenceQueue
的机制,达到垃圾回收的目的。
强引用 > 软引用 > 弱引用 > 虚引用
⑴强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
⑵软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
⑶弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
⑷虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
由于引用和内存回收关系紧密。下面,先通过实例对内存回收有个认识;然后,进一步通过引用实例加深对引用的了解。