gc过程中reference对象的处理,gcreference
gc过程中reference对象的处理,gcreference
引用对象结构
数据结构定义在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 ,分别切换注释。相应的的运行结果都会不一样。可以了解一下不同的运行结果。
用户点评