问题
在分析前,先抛几个问题?
1.网上大多数文章对于软引用的介绍是:在内存不足的时候才会被回收,那内存不足是怎么定义的?什么才叫内存不足?
2.网上大多数文章对于虚引用的介绍是:形同虚设,虚引用并不会决定对象的生命周期。主要用来跟踪对象被垃圾回收器回收的活动。真的是这样吗?
3.虚引用在Jdk中有哪些场景下用到了呢?
Reference
我们先看下Reference.java中的几个字段
public abstract class Reference<T> { //引用的对象
private T referent;
//回收队列,由使用者在Reference的构造函数中指定
volatile ReferenceQueue<? super T> queue; //当该引用被加入到queue中的时候,该字段被设置为queue中的下一个元素,以形成链表结构
volatile Reference next; //在GC时,JVM底层会维护一个叫DiscoveredList的链表,存放的是Reference对象,discovered字段指向的就是链表中的下一个元素,由JVM设置
transient private Reference<T> discovered;
//进行线程同步的锁对象
static private class Lock { } private static Lock lock = new Lock(); //等待加入queue的Reference对象,在GC时由JVM设置,会有一个java层的线程(ReferenceHandler)源源不断的从pending中提取元素加入到queue
private static Reference<Object> pending = null;
}一个Reference对象的生命周期如下:

image
主要分为Native层和Java层两个部分。
Native层在GC时将需要被回收的Reference对象加入到DiscoveredList中(代码在referenceProcessor.cpp中process_discovered_references方法),然后将DiscoveredList的元素移动到PendingList中(代码在referenceProcessor.cpp中enqueue_discovered_ref_helper方法),PendingList的队首就是Reference类中的pending对象。 具体代码就不分析了,有兴趣的同学可以看看这篇文章。
看看Java层的代码
private static class ReferenceHandler extends Thread {
... public void run() { while (true) {
tryHandlePending(true);
}
}
}
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c; try { synchronized (lock) { if (pending != null) {
r = pending; //如果是Cleaner对象,则记录下来,下面做特殊处理
c = r instanceof Cleaner ? (Cleaner) r : null; //指向PendingList的下一个对象
pending = r.discovered;
r.discovered = null;
} else { //如果pending为null就先等待,当有对象加入到PendingList中时,jvm会执行notify
if (waitForNotify) {
lock.wait();
} // retry if waited
return waitForNotify;
}
}
}
... // 如果时CLeaner对象,则调用clean方法进行资源回收
if (c != null) {
c.clean(); return true;
} //将Reference加入到ReferenceQueue,开发者可以通过从ReferenceQueue中poll元素感知到对象被回收的事件。
ReferenceQueue<? super Object> q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); return true;
}流程比较简单:就是源源不断的从PendingList中提取出元素,然后将其加入到ReferenceQueue中去,开发者可以通过从ReferenceQueue中poll元素感知到对象被回收的事件。
另外需要注意的是,对于Cleaner类型(继承自虚引用)的对象会有额外的处理:在其指向的对象被回收时,会调用clean方法,该方法主要是用来做对应的资源回收,在堆外内存DirectByteBuffer中就是用Cleaner进行堆外内存的回收,这也是虚引用在java中的典型应用。
看完了Reference的实现,再看看几个实现类里,各自有什么不同。
SoftReference
public class SoftReference<T> extends Reference<T> {
static private long clock;
private long timestamp;
public SoftReference(T referent) { super(referent); this.timestamp = clock;
}
public SoftReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); this.timestamp = clock;
} public T get() {
T o = super.get(); if (o != null && this.timestamp != clock) this.timestamp = clock; return o;
}
}软引用的实现很简单,就多了两个字段:clock和timestamp。clock是个静态变量,每次GC时都会将该字段设置成当前时间。timestamp字段则会在每次调用get方法时将其赋值为clock(如果不相等且对象没被回收)。
那这两个字段的作用是什么呢?这和软引用在内存不够的时候才被回收,又有什么关系呢?
这些还得看JVM的源码才行,因为决定对象是否需要被回收都是在GC中实现的。
size_tReferenceProcessor::process_discovered_reflist(
DiscoveredList refs_lists[],
ReferencePolicy* policy, bool clear_referent,
BoolObjectClosure* is_alive,
OopClosure* keep_alive,
VoidClosure* complete_gc,
AbstractRefProcTaskExecutor* task_executor)
{
... //还记得上文提到过的DiscoveredList吗?refs_lists就是DiscoveredList。
//对于DiscoveredList的处理分为几个阶段,SoftReference的处理就在第一阶段
... for (uint i = 0; i < _max_num_q; i++) {
process_phase1(refs_lists[i], policy,
is_alive, keep_alive, complete_gc);
}
...
}//该阶段的主要目的就是当内存足够时,将对应的SoftReference从refs_list中移除。voidReferenceProcessor::process_phase1(DiscoveredList& refs_list,
ReferencePolicy* policy,
BoolObjectClosure* is_alive,
OopClosure* keep_alive,
VoidClosure* complete_gc) {
DiscoveredListIterator iter(refs_list, keep_alive, is_alive); // Decide which softly reachable refs should be kept alive.
while (iter.has_next()) {
iter.load_ptrs(DEBUG_ONLY(!discovery_is_atomic() /* allow_null_referent */)); //判断引用的对象是否存活
bool referent_is_dead = (iter.referent() != NULL) && !iter.is_referent_alive(); //如果引用的对象已经不存活了,则会去调用对应的ReferencePolicy判断该对象是不时要被回收
if (referent_is_dead &&
!policy->should_clear_reference(iter.obj(), _soft_ref_timestamp_clock)) { if (TraceReferenceGC) {
gclog_or_tty->print_cr("Dropping reference (" INTPTR_FORMAT ": %s" ") by policy",
(void *)iter.obj(), iter.obj()->klass()->internal_name());
} // Remove Reference object from list
iter.remove(); // Make the Reference object active again
iter.make_active(); // keep the referent around
iter.make_referent_alive();
iter.move_to_next();
} else {
iter.next();
}
}
...
}refs_lists中存放了本次GC发现的某种引用类型(虚引用、软引用、弱引用等),而process_discovered_reflist方法的作用就是将不需要被回收的对象从refs_lists移除掉,refs_lists最后剩下的元素全是需要被回收的元素,最后会将其第一个元素赋值给上文提到过的Reference.java#pending字段。
ReferencePolicy一共有4种实现:NeverClearPolicy,AlwaysClearPolicy,LRUCurrentHeapPolicy,LRUMaxHeapPolicy。其中NeverClearPolicy永远返回false,代表永远不回收SoftReference,在JVM中该类没有被使用,AlwaysClearPolicy则永远返回true,在referenceProcessor.hpp#setup方法中中可以设置policy为AlwaysClearPolicy,至于什么时候会用到AlwaysClearPolicy,大家有兴趣可以自行研究。
LRUCurrentHeapPolicy和LRUMaxHeapPolicy的should_clear_reference方法则是完全相同:
bool LRUMaxHeapPolicy::should_clear_reference(oop p,
jlong timestamp_clock) {
jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p);
assert(interval >= 0, "Sanity check"); // The interval will be zero if the ref was accessed since the last scavenge/gc.
if(interval <= _max_interval) { return false;
} return true;
}timestamp_clock就是SoftReference的静态字段clock,java_lang_ref_SoftReference::timestamp(p)对应是字段timestamp。如果上次GC后有调用SoftReference#get,interval值为0,否则为若干次GC之间的时间差。
_max_interval则代表了一个临界值,它的值在LRUCurrentHeapPolicy和LRUMaxHeapPolicy两种策略中有差异。
void LRUCurrentHeapPolicy::setup() {
_max_interval = (Universe::get_heap_free_at_last_gc() / M) * SoftRefLRUPolicyMSPerMB;
assert(_max_interval >= 0,"Sanity check");
}void LRUMaxHeapPolicy::setup() { size_t max_heap = MaxHeapSize;
max_heap -= Universe::get_heap_used_at_last_gc();
max_heap /= M;
_max_interval = max_heap * SoftRefLRUPolicyMSPerMB;
assert(_max_interval >= 0,"Sanity check");
}其中SoftRefLRUPolicyMSPerMB默认为1000,前者的计算方法和上次GC后可用堆大小有关,后者计算方法和(堆大小-上次gc时堆使用大小)有关。
看到这里你就知道SoftReference到底什么时候被被回收了,它和使用的策略(默认应该是LRUCurrentHeapPolicy),堆可用大小,该SoftReference上一次调用get方法的时间都有关系。
WeakReference
public class WeakReference<T> extends Reference<T> { public WeakReference(T referent) { super(referent);
} public WeakReference(T referent, ReferenceQueue<? super T> q) { super(referent, q);
}
}可以看到WeakReference在Java层只是继承了Reference,没有做任何的改动。那referent字段是什么时候被置为null的呢?要搞清楚这个问题我们再看下上文提到过的process_discovered_reflist方法:
size_t
ReferenceProcessor::process_discovered_reflist(
DiscoveredList refs_lists[],
ReferencePolicy* policy,
bool clear_referent,
BoolObjectClosure* is_alive,
OopClosure* keep_alive,
VoidClosure* complete_gc,
AbstractRefProcTaskExecutor* task_executor)
{
... //Phase 1:将所有不存活但是还不能被回收的软引用从refs_lists中移除(只有refs_lists为软引用的时候,这里policy才不为null)
if (policy != NULL) { if (mt_processing) { RefProcPhase1Task phase1(*this, refs_lists, policy, true /*marks_oops_alive*/);
task_executor->execute(phase1);
} else { for (uint i = 0; i < _max_num_q; i++) {
process_phase1(refs_lists[i], policy,
is_alive, keep_alive, complete_gc);
}
}
} else { // policy == NULL
assert(refs_lists != _discoveredSoftRefs, "Policy must be specified for soft references.");
} // Phase 2:
// 移除所有指向对象还存活的引用
if (mt_processing) { RefProcPhase2Task phase2(*this, refs_lists, !discovery_is_atomic() /*marks_oops_alive*/);
task_executor->execute(phase2);
} else { for (uint i = 0; i < _max_num_q; i++) {
process_phase2(refs_lists[i], is_alive, keep_alive, complete_gc);
}
} // Phase 3:
// 根据clear_referent的值决定是否将不存活对象回收
if (mt_processing) { RefProcPhase3Task phase3(*this, refs_lists, clear_referent, true /*marks_oops_alive*/);
task_executor->execute(phase3);
} else { for (uint i = 0; i < _max_num_q; i++) {
process_phase3(refs_lists[i], clear_referent,
is_alive, keep_alive, complete_gc);
}
} return total_list_count;
}voidReferenceProcessor::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();
iter.load_ptrs(DEBUG_ONLY(false /* allow_null_referent */)); if (clear_referent) { // NULL out referent pointer
//将Reference的referent字段置为null,之后会被GC回收
iter.clear_referent();
} else { // keep the referent around
//标记引用的对象为存活,该对象在这次GC将不会被回收
iter.make_referent_alive();
}
...
}
...
}
作者:往之farmer
链接:https://www.jianshu.com/p/827c99e42c5b
随时随地看视频