gc过程中reference对象的处理

java admin 104000 0 评论

引用对象结构

数据结构定义在referenceProcessor.hpp。定义了以下4种类型的结构。

_discoveredSoftRefs
_discoveredWeakRefs
_discoveredFinalRefs
_discoveredPhantomRefs

每一个结构相对应的数据结构为 DiscoveredList,可以理解为与具体的Reference相同的结构,类似一个处理链表,里面的每一个节点都对应着java中的reference对象。这里仍然采用头指针+length的结构来持有所有需要处理的reference对象。在这里面存放的对象都表示在相应的处理过程中还没有被放入java Reference中pending结构的对象。

从总体上的处理逻辑来看,可以理解为。在整个gc过程中,首先在jvm内部维护一套需要被放到pending中的引用链,然后处理这些引用链,处理完之后将相应的数据重新附到pending中,清除jvm内部数据。这样达到一个reference的处理过程。

对象何时放入DiscoveredList中

在gc的某个阶段(跟踪了大部分代码,由于对c++不熟,没有找到源头),可以理解为,所有的reference对象在创建时是被特殊对待的,相应的对象结构由 InstanceRefKlass 来持有,因此在gc的过程中,会触发所有对象的 oop_follow_contents 操作,此操作可以认为是对一些额外对象的处理工作。在refClass的处理逻辑中,会调用ReferenceProcessor::discover_reference方法(文件referenceProcessor.cpp),此方法的的作用就在于将相应的对象进行添加到discoveredList当中。其相应的调用主逻辑如下所示

bool ReferenceProcessor::discover_reference(oop obj, ReferenceType rt) {

// Make sure we are discovering refs (rather than processing discovered refs).

// We only discover active references.
//软引用如果还不需要回收,则直接返回

if (rt == REF_SOFT) {

}
// Get the right type of discovered queue head.

//找到之前的几种引用类型的链表

DiscoveredList* list = get_discovered_list(rt);
//采用多线程处理方式(并不表示以下的操作是多线程工作的)添加到链表当中

if (_discovery_is_mt) {
add_to_discovered_list_mt(*list, obj, discovered_addr);

return true;
}

然后切换到相应的add_to_discovered_list方法,简要的逻辑如下所示:

ReferenceProcessor::add_to_discovered_list_mt(DiscoveredList& refs_list,





oop

 obj,





HeapWord*
 discovered_addr) {

//找到头节点




oop current_head = refs_list.head();

oop next_discovered = (current_head != NULL) ? current_head : obj;

//尝试判断此obj的discovered对象是否仍是null,如果不是则表示另一个线程已经处理了

oop retest = oopDesc::atomic_compare_exchange_oop(next_discovered, discovered_addr,






NULL);

if (retest == NULL) {
//进行正式的更改操作,即将obj重新设置为list中的头节点,长度+1
refs_list.set_head(obj);
refs_list.inc_length(1);

//这里的_discovered_list_needs_barrier值为true, 则下面的操作即表示设置obj的 discovered 对象为之前的头节点,这样即形成一个后进先出的处理链条,即与Reference结构相对应
if (_discovered_list_needs_barrier) {

_bs->write_ref_field((void*)discovered_addr, next_discovered);
}

}

在上面的处理逻辑中,可以看出在jvm内部,并没有针对Reference重新建立相应的处理结构来维护相应的处理链,而是直接采用java中的Reference对象链来处理,只不过这些对象的关系由jvm在内部进行处理,而且这些处理的对象的内部结构因为也没有在被java其它对象所访问。

在java中 discovered对象只会被方法 tryHandlePending 修改,而此方法只会处理pending链中的对象。而在上面的处理过程中,相应的对象并没有在pending中,因此两个处理过程是不相干的。

在完成了相应的对象入栈之后,下一个阶段就是正式的引用放到pending中的过程。而在这个过程中,有些对象还需要被重新标记或处理。

cms执行部分

在之前的引用入栈之后,在cms的FinalMarking阶段,会进行各项引用的处理工作,即重新处理引用信息,然后附到pending上去。整个处理逻辑以及调用链如下所示:

FinalMarking 最终标识阶段
VM_CMS_Final_Remark 进行最终标识这一步骤
do_CMS_operation 进行指定的操作
CMS_op_checkpointRootsFinal 指定步骤语义
checkpointRootsFinal
checkpointRootsFinalWork
refProcessingWork 整个引用处理逻辑
enqueue_discovered_references 对enqueue_discovered_ref_helper的封装调用
enqueue_discovered_ref_helper 辅助工具类 找到pending节点,准备替换,然后又切换回来
enqueue_discovered_reflists 正式的替换pending节点

进入到标记阶段

在方法 CMSCollector::collect_in_background (文件concurrentMarkSweepGeneration.cpp)的处理中,封装了主要的处理逻辑,通过case来进入到不同的处理逻辑。我们这里关心的步骤为FinalMarking,这一阶段,相应的逻辑会切换到 VM_CMS_Final_Remark 这一个处理过程。代码如下所示:

case FinalMarking:

{
ReleaseForegroundGC x(this);

VM_CMS_Final_Remark final_remark_op(this);
VMThread::execute(&final_remark_op);

}

在 VM_CMS_Final_Remark (文件vmCMSOperations.cpp) 的过程中,在一些简单处理之后,又将逻辑交给 CMSCollector::do_CMS_operation (文件concurrentMarkSweepGeneration.cpp) 来进行,然后里面又根据当前语义阶段,判定,最终进入到 CMS_op_checkpointRootsFinal 阶段,相应的代码如下简单所示:

void CMSCollector::do_CMS_operation(CMS_op_type op, GCCause::Cause gc_cause) {

gclog_or_tty->date_stamp(PrintGC && PrintGCDateStamps);
switch (op) {
case CMS_op_checkpointRootsFinal: {

checkpointRootsFinal(true,

// asynch


false,
 // !clear_all_soft_refs


false);
// !init_mark_was_synchronous

if (PrintGC) {
_cmsGen->printOccupancy("remark");

}
}

在相应的逻辑中,一些简单工作之后,又切换至 checkpointRootsFinalWork 方法当中,此方法承担了在最终回收之前的大部分工作。这里我们仅关心引用如何处理这一段,相应的方法简要如下所示:

{

NOT_PRODUCT(GCTraceTime ts("refProcessingWork", PrintGCDetails, false, _gc_timer_cm);)

refProcessingWork(asynch, clear_all_soft_refs);
}

即会进入到引用处理工作当中,这里的clear_all_soft即表示是否要清除softReference中的对象的相应逻辑(有一些策略在里面)。

主要的引用处理工作,包括2个部分,一个是对引用对象的处理,如对一些重新有效的对象需要排除在引用链外,或者是软引用清除,以及weak引用设置引用为null等。另一个步骤则是正式的pending对接工作。整个引用的处理代码如下所示:

void CMSCollector::refProcessingWork(bool asynch, bool clear_all_soft_refs) {
ResourceMark rm;

HandleMark
 hm;
// 重新设置相应的软引用清除策略

// Process weak references.

rp->setup_policy(clear_all_soft_refs);

verify_work_stacks_empty();
{
//引用处理过程
GCTraceTime t("weak refs processing", PrintGCDetails, false, _gc_timer_cm);

ReferenceProcessorStats stats;
//正式的引用处理过程

stats = rp->process_discovered_references(&_is_alive_closure,




&cmsKeepAliveClosure,




&cmsDrainMarkingStackClosure,




NULL,




_gc_timer_cm);

}
if (rp->processing_is_mt()) {

//因为之前收集过程是多线程的,这里引用链可能并不平(即每个链长度可能并不相同)
rp->balance_all_queues();

//正式的引用链重新附到pending对象上
CMSRefProcTaskExecutor task_executor(*this);
rp->enqueue_discovered_references(&task_executor);

}
}

引用处理过程process_discovered_references

文件为referenceProcessor.cpp,相应的处理过程即针对每一种引用,通过统一的调用逻辑来进行处理,相应的调用处理简单如下所示:

ReferenceProcessorStats ReferenceProcessor::process_discovered_references(

BoolObjectClosure*
 is_alive,

OopClosure*


keep_alive,

VoidClosure*

 complete_gc,

AbstractRefProcTaskExecutor* task_executor,

GCTimer*

 gc_timer) {
// Soft references 处理软引用,传入了相应的软引用清除策略

size_t soft_count = 0;

{
GCTraceTime tt("SoftReference", trace_time, false, gc_timer);
soft_count =

process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true,




 is_alive, keep_alive, complete_gc, task_executor);

}
// Weak references,逻辑与软引用相同,不过这里明确表示清除引用对象(即在程序中通过queue拿到的对象中的referent对象肯定为null)

size_t weak_count = 0;

{
GCTraceTime tt("WeakReference", trace_time, false, gc_timer);
weak_count =

process_discovered_reflist(_discoveredWeakRefs, NULL, true,




 is_alive, keep_alive, complete_gc, task_executor);

}
// Final references 处理finalize对象

// Phantom references 处理phantomReference对象

// Weak global JNI references
return ReferenceProcessorStats(soft_count, weak_count, final_count, phantom_count);
}

相应的处理过程又分为3个阶段。即可理解为并不是所有的对象都需要放到pending中,这里即是将不需要放到pending中的对象移除掉(下一次gc再处理)。相应的处理过程如下代码所示

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 (soft refs only):

// . Traverse the list and remove any SoftReferences whose

//
 referents are not alive, but that should be kept alive for

//
 policy reasons. Keep alive the transitive closure of all

//
 such referents.

//如上注解所说,即有的软引用并不需要被处理,因此需要从链中排除掉
process_phase1(refs_lists[i], policy,



 is_alive, keep_alive, complete_gc);
// Phase 2:

// . Traverse the list and remove any refs whose referents are alive.

// 有些reference对象在之前的标记中又是alive了,因此需要排除掉
process_phase2(refs_lists[i], is_alive, keep_alive, complete_gc);
// Phase 3:

// . Traverse the list and process referents as appropriate.

// 如果需要设置referent为null,则在第3个阶段处理
RefProcPhase3Task phase3(*this, refs_lists, clear_referent, true /*marks_oops_alive*/);
task_executor->execute(phase3);
return total_list_count;
}

引用重新附到pending上 enqueue_discovered_references

相应的逻辑先通过转向 enqueue_discovered_ref_helper,然后最终进入到 ReferenceProcessor::enqueue_discovered_reflists 中(文件referenceProcessor.cpp). 相应的逻辑很简单,如下所示:

enqueue_discovered_reflist(_discovered_refs[i], pending_list_addr);
_discovered_refs[i].set_head(NULL);
_discovered_refs[i].set_length(0);

可以理解为即将相应的引用链附到pending上,然后 当前处理链清空(设置head=null以及length为0),附到pending上的过程,可以理解为就是将原来Reference中的pending和当前的处理链对接起来即可。因为两者都是reference对象,因此相应的处理可以理解为找到当前处理链中的末尾对象,然后设置末尾的discovered为pending,并且将pending重新修改为处理链的头节点即可。对接过程可以理解为 pending = jvmRList+pending这个过程。相应的详细代码如下所示:

void ReferenceProcessor::enqueue_discovered_reflist(DiscoveredList& refs_list,






HeapWord* pending_list_addr) {

// 以下的注解即可相应的处理逻辑

// Given a list of refs linked through the "discovered" field

// (java.lang.ref.Reference.discovered), self-loop their "next" field

// thus distinguishing them from active References, then

// prepend them to the pending list.
if (TraceReferenceGC && PrintGCDetails) {
gclog_or_tty->print_cr("ReferenceProcessor::enqueue_discovered_reflist list "


INTPTR_FORMAT, (address)refs_list.head());

}
oop obj = NULL;

oop next_d = refs_list.head();
// Walk down the list, self-looping the next field
// so that the References are not considered active.
while (obj != next_d) {

obj = next_d;

next_d = java_lang_ref_Reference::discovered(obj);
//这里为pending状态,因此设置相应的next指针

// Self-loop next, so as to make Ref not active.

java_lang_ref_Reference::set_next(obj, obj);
if (next_d == obj) {
// obj is last next next_discover=自己,即当前节点为最后一个节点
// Swap refs_list into pendling_list_addr and
// set obj's discovered to what we read from pending_list_addr.
//这里即重新设置pending值,即指向处理链的头节点
oop old = oopDesc::atomic_exchange_oop(refs_list.head(), pending_list_addr);
// Need oop_check on pending_list_addr above;
// see special oop-check code at the end of
// enqueue_discovered_reflists() further below.
//当前处理链尾节点设置next_discover节点为pending节点
java_lang_ref_Reference::set_discovered(obj, old); // old may be NULL

}
}
}

总结:

经过与整个gc过程相对接,整个引用的处理过程由jvm最终结束为Reference中的pending,由整个过程可以看出。在整个gc过程中,jvm针对各种弱引用对象的特殊处理,包括对象类型class的特殊对待,然后是各个过程过程中的特殊标识过程,最后是与java中queue的链接,最终完成整个弱引用处理。

通过了解jvm内部c++代码的实现过程,也可以更清楚地了解jvm内部的工作原理,一些流程也更方便地在后续对各种弱引用的处理过程更加了解,运用时也更加熟悉。

最后,附一个简单的java代码

public class T {
private static Set<WeakReference> set = Sets.newIdentityHashSet();
public static ReferenceQueue queue = new ReferenceQueue();

private byte[] bytes = new byte[1024_000];
private WeakReference reference = new WeakReference<T>(this, queue); //1
//

private WeakReference reference = new WeakReference<T>(this, queue) {}; //2

public T() {
set.add(reference); //3
//4 注释掉上一行
}

public static void main(String[] args) throws Exception {
for(int i = 0; i < 10_000; i++) {


new T();
}

int i = 0;
while(queue.remove(2000) != null) {


i++;
}

System.out.println("->" + i);
}
}

在以上的代码中,代码点 1和2 3和4 ,分别切换注释。相应的的运行结果都会不一样。可以了解一下不同的运行结果。

转载请注明: 飞嗨_分享互联网 » gc过程中reference对象的处理

赞 (0) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽

高效,专业,符合SEO

联系我们