本文出自:【InTheWorld的博客】 (欢迎留言、交流)
1. Reference的基本介绍
reference的中文含义是“引用”。由于本文所基于的HotSpot虚拟机主要使用C++开发,因此我担心有人会把C++的引用和这里的reference混为一谈。所以,我会尽量使用reference(首字母小写)来表述”引用“这个概念。通常我们写下如下的语句: Object obj; 其实就是定义了一个reference。我可以很直白的说出一个结论——在32位机器上HotSpot的reference就是一个32bit的指针。如下图所示,reference指向一个堆空间的实际对象。这种常用的reference,我们给它起个名字——StrongReference。之所以这样叫,是为了体现它和其他Reference(即将登场)的联系与区别。
在java.lang.ref包下,有这样几个类:SoftReference、WeakReference以及PhantomReference。它们之间的继承关系图如下所示:
以WeakReference为例,我们来看看Reference的用法。有如下的代码片段:
SoftReference sr = new SoftReference (new Employee ()); Employee em = sr.get();
第一行就定义了一个关于Employee的软引用,换言之sr和new Employee()产生的匿名对象之间形成了软引用关系。第二行代码展示了软引用的使用方式,也很简单使用get()方法。而软应用的要点在于这个get()方法可能返回null,换言之软引用所指向的东西是可以被gc清理掉的。下面这张图展示了软引用的内存布局:
其中,SoftReference实例也是一个堆内存中的对象,它被sr这个普通的StrongReference所引用,同时它还通过软引用指向图中的Emplyee对象。其实在内存布局方面,WeakReference、PhantomReference与SoftReference是非常相似的,它们主要的区别在于gc的时机和get()方法返回值不同。比如,软引用会在内存紧张的时候被释放,而弱引用会在gc的时候立即被释放。
引用类型 | 取得目标对象方式 | 垃圾回收条件 | 是否可能内存泄漏 |
---|---|---|---|
强引用 | 直接调用 | 不回收 | 可能 |
软引用 | 通过 get() 方法 | 视内存情况回收 | 不可能 |
弱引用 | 通过 get() 方法 | 永远回收 | 不可能 |
虚引用 | 无法取得 | 不回收 | 可能 |
上面这张表展示了不同种类的Reference的特性对比。此外还有一个知识点值得注意,就是Reference.get()方法拿到的是一个强引用,所以不会出现get到一个非null值却在之后被gc掉的情况(在强引用作用域内)。另外,Reference中指向the referent的内部引用域其实是会被多个读、多个写的。Reference.get()是读的情况,它没有加锁,gc对引用域会存在写操作,但gc的操作都是在锁内完成的,这个锁就是全局的gc锁。因此,Reference是不存在同步错误的。
2. Reference语义的实现
前面用不少篇幅来介绍了Reference的基本工作方式,下面我们来研究下HotSpot VM是如何实现Reference的基本语义的。话不多说,先看看java.lang.ref包里面的代码。、
public abstract class Reference{ //Reference指向的对象 private T referent; /* Treated specially by GC */ //Reference所指向的队列 volatile ReferenceQueue super T> queue; @SuppressWarnings("rawtypes") Reference next; transient private Reference discovered; /* used by VM */ Reference(T referent) { this(referent, null); } Reference(T referent, ReferenceQueue super T> queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue; } }
Reference的queue的作用是为了监视引用的状态,不算是一种常用的模式,第一个构造函数更为常用。这java.lang.ref的包里面,并看不出Reference和gc是如何协同作用的,而且也看不到任何native方法。然而”Treated specially by GC ”这句注释还是暴露了不少信息的。不同类型的Reference语义其实其实是由GC直接实现的。听起来有点悬。还是根据源代码来分析吧!下面根据g1垃圾收集器分析Reference的语义实现:
openjdk\hotspot\src\share\vm\gc_implementation\g1\g1CollectedHeap.cpp
g1ColloctedHeap.cpp是g1垃圾收集器的重要实现部分。在g1ColloctedHeap类中有下面这个方法,简化的代码如下:
void G1CollectedHeap::process_discovered_references(uint no_of_gc_workers) { double ref_proc_start = os::elapsedTime(); //获取ReferenceProcessor ReferenceProcessor* rp = _ref_processor_stw; ReferenceProcessorStats stats; if (!rp->processing_is_mt()) { // 串行Reference处理... stats = rp->process_discovered_references(&is_alive, &keep_alive, &drain_queue, NULL, _gc_timer_stw); } else { // 并行Reference处理 G1STWRefProcTaskExecutor par_task_executor(this, workers(), _task_queues, no_of_gc_workers); stats = rp->process_discovered_references(&is_alive, &keep_alive, &drain_queue, &par_task_executor, _gc_timer_stw); } }
这个函数会调用ReferenceProcessor的process_discovered_references()方法。ReferenceProcessor,顾名思义就是处理Reference的。到ReferenceProcessor的代码里面一看究竟吧!process_discovered_references()方法看起来非常直接了当,可以非常明显看出它完成了几种Reference的处理。
ReferenceProcessorStats ReferenceProcessor::process_discovered_references(/* */) { // Soft references size_t soft_count = 0; { soft_count = process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true, is_alive, keep_alive, complete_gc, task_executor); } // Weak references size_t weak_count = 0; /* */ // Phantom references size_t phantom_count = 0; /* */ return ReferenceProcessorStats(soft_count, weak_count, final_count, phantom_count); }
由于几种Reference的处理其实比较类似,我就只贴了SoftReference的代码。process_discoverd_reflist()方法实现了真正的Reference处理工作。这个方法会调用三个阶段的代码,其中第三个阶段会去gc非Strong Reference的referent引用。这个第三阶段的函数就是process_phase3()。它的大致代码如下:
void ReferenceProcessor::process_phase3(DiscoveredList& refs_list, bool clear_referent, BoolObjectClosure* is_alive, OopClosure* keep_alive, VoidClosure* complete_gc) { ResourceMark rm; DiscoveredListIterator iter(refs_list, keep_alive, is_alive); while (iter.has_next()) { iter.update_discovered(); if (clear_referent) { // NULL out referent pointer iter.clear_referent(); } else { // keep the referent around iter.make_referent_alive(); } assert(iter.obj()->is_oop(UseConcMarkSweepGC), "Adding a bad reference"); iter.next(); } // Remember to update the next pointer of the last ref. iter.update_discovered(); // Close the reachable set complete_gc->do_void(); }
不知道为什么,我很喜欢看到循环。大概是觉得它活干的比较多吧!process_phase3()的这个while循环,就完成了对Reference的处理。iter.clear_referenct()就是把Reference的referent域赋值为null,然后referent所指向的堆上对象就要面临被gc的命运了。
写到这里,可以看出gc对Reference的大致处理逻辑。然而,仍然有很多Reference的内容没有分析到,比如几种Reference的差异化处理,没有分析。WeakReference和SoftRefence的处理逻辑差异,主要体现在Reference发现的阶段,简单来讲SoftReference要在内存吃紧的时候才会被加到DiscoverList里面。而PhantomReference的作用是跟踪对象被垃圾回收器回收的活动,它需要和ReferenceQueue配合使用,绝大多数开发者都不需要使用它。还有就是ReferenceQueue的内容,这里也没有涉及。如果有同学感兴趣,可以按着上面的代码去跟跟。
~~~The End~~~
参考资料:
[1]. http://www.javaworld.com/article/2073891/java-se/java-se-trash-talk-part-2.html
[2]. https://www.ibm.com/developerworks/cn/java/j-lo-langref/
博主你好,这篇文章已经发表一年多了,不知道你后续还有没有继续了解Java引用这一块。
我觉得这篇文章里对引用的理解可能有一些错误。首先并不是弱引用类型引用的对象总是会被回收,对象是否回收取决于它的可达性(Reachability),而弱引用类型引用的对象并不一定就是弱可达的。弱可达的对象很可能会在下一次GC时被回收,但也不是绝对的。所以严谨一点的话,文章里的那个表格最左边不是强引用、软引用等,而是强可达、软可达等。
虚引用(如果表示的是虚可达的意思的话)也不是不回收,我认为恰恰相反,而是已经被回收。虚引用的作用主要就是用来判断被引用的对象是否已经被回收。具体可以看虚引用类型的类注释:Phantom reference objects, which are enqueued after the collector determines that their referents may otherwise be reclaimed。
关于是否可能引起内存泄漏,我认为使用弱引用和虚引用最起码不会引起新的内存泄漏,已存在的内存泄漏应该也和它们无关,强引用不用说的,而软引用如果使用不当的话是可能会引起内存泄漏的。因为软可达对象并不能保证会被及时回收,根据内存情况而定其实是最不确定的情况,能确定的只有:在虚拟机发生OOM之前一定会保证先释放软可达的对象,至于真实情况下什么时候才会释放取决于各回收器的实现。
我说了这么多其实是在纠结于文章中的那个表格,但正是这一点可能会影响文章的严谨性。另外,博主最后给的参考资料的第二个我建议还是不要参考为好,这篇虽是发表在IBM开发社区上,但是其中的错误还是蛮多的,不值得参考。
是的,那个表格的确是有一些歧义,弱引用的对象也可能被强引用。可达性是最标准的判据,这点我同意。写这篇文章的时候大致看了下jvm的引用实现,但基本还是停留在脉络上,不够深入。JVM已经有一段时间没看了,工作比较忙,要学的东西太多了。。。
引用通知: 一次ThreadLocal源码解析之旅 – IT汇 /