本文出自:【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<T> {
//Reference指向的对象
private T referent; /* Treated specially by GC */
//Reference所指向的队列
volatile ReferenceQueue<? super T> queue;
@SuppressWarnings("rawtypes")
Reference next;
transient private Reference<T> 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/
回复 wf 取消回复