jvm源码阅读笔记[1]:如何触发一次CMS回收
2017-08-20 18:03
1081 查看
从零开始看源码,旨在从源码验证书上的结论,探索书上未知的细节。有疑问欢迎留言探讨
个人源码地址:https://github.com/FlashLightNing/openjdk-notes
还有一个openjdk6,7,8,9的地址:https://github.com/dmlloyd/openjdk
jvm源码阅读笔记[1]:如何触发一次CMS回收
jvm源码阅读笔记[2]:你不知道的晋升阈值TenuringThreshold详解
jvm源码阅读笔记[3]:从内存分配到触发GC的细节
jvm源码阅读笔记[4]:从GC说到vm operation
jvm源码阅读笔记[5]:内存分配失败触发的GC究竟对内存做了什么?
对于配置使用CMS回收器的应用,用jstack pid | grep GC 可以发现,有一个名为Concurrent Mark-Sweep GC Thread的线程。
这个线程的作用呢,就是每隔2秒钟,就检查一下老年代是否需要回收,如果需要,则就会进行一次CMS回收。为什么这么说呢?来看看这个线程对应的源码:源码地址
再来看看比较关键的线程的run()方法:
其中,关键的是调用的sleepBeforeNextCycle()方法:
根据CMSWaitDuration参数,我们可以知道,该cms线程会一直在sleepBeforeNextCycle()方法内循环,然后阻塞完2000毫秒后,判断是否需要进行一次GC。如果不需要,继续走上面的流程。如果需要,就结束sleepBeforeNextCycle()方法,然后在run方法中,进行一次_collector->collect_in_background()调用,从而回收老年代。
需要注意的是,这里进行的是background式的CMS GC。还有一种是foreground式的CMS GC.具体它们的区别,可以参考这篇文章:http://lovestblog.cn/blog/2015/05/07/system-gc/
所以总结起来就是:当堆初始化完成后,就会创建一个cms的 gc线程。这个线程的作用,就是根据配置的CMSWaitDuration时间(默认2000ms),轮询判断老年代是否需要进行GC(根据一定的条件,比如占用率达到阈值)。如果需要GC,则进行一次background式的CMS GC。
那么,再来看看满足哪些条件的时候jvm会认为需要进行一次CMS GC呢?关键看看concurrentMarkSweepGeneration.cpp中的shouldConcurrentCollect()方法。(concurrentMarkSweepGeneration.cpp地址)
这个方法比较长,主要有以下几种方式触发:
(1) 请求进行一次fullgc,如调用System.gc时
(2) 当没有设置UseCMSInitiatingOccupancyOnly时,会动态计算。如果完成CMS回收的所需要的预计的时间小于预计的CMS回收的分代填满的时间,就进行回收
(3) 调用should_concurrent_collect()方法返回true(后面会讲都有哪些情况时该方法会返回true)
(4) 如果预计增量式回收会失败时,也会触发一次回收。
(5) 如果metaSpace认为需要回收metaSpace区域,也会触发一次cms回收
(6) 如果设置了CMSTriggerInterval参数。该参数默认值-1,表示CMS回收的间隔。如果=0表示一直触发CMSGC。如果>0,则会判断从开始CMSGC到现在经过的时间是否大于该参数的时间。若大于,则会进行一次CMSGC。
如果都不满足,则返回false,表示不满足进行GC的条件。
再来看看第3条,看看什么时候should_concurrent_collect()会返回true。
可以发现,当
(1)堆当前的使用率>配置的initiating_occupancy,也就是我们经常配置的-XX:CMSInitiatingOccupancyFraction=80。
(2)当设置了UseCMSInitiatingOccupancyOnly=true,也就是只允许使用率达到阈值才进行回收时,不会判断其他条件。
(3)当堆扩容的原因是_satisfy_allocation时。
(4)CompactibleFreeListSpace认为需要回收时。
总结起来,就是下面这么多原因:
(1)请求进行一次fullgc,如调用System.gc时
(2)当没有设置UseCMSInitiatingOccupancyOnly时,会动态计算。如果完成CMS回收的所需要的预计的时间小于预计的CMS回收的分代填满的时间,就进行回收
(3.1)堆当前的使用率>配置的initiating_occupancy,也就是我们经常配置的-XX:CMSInitiatingOccupancyFraction=80。
(3.2)当设置了UseCMSInitiatingOccupancyOnly=true,也就是只允许使用率达到阈值才进行回收时,不会判断其他条件。
(3.3)当堆扩容的原因是_satisfy_allocation时。
(3.4)CompactibleFreeListSpace认为需要回收时。
(4)如果预计增量式回收会失败时,也会触发一次回收。
(5)如果metaSpace认为需要回收metaSpace区域,也会触发一次cms回收
(6)如果设置了CMSTriggerInterval参数。该参数默认值-1,表示CMS回收的间隔。如果=0表示一直触发CMSGC。如果>0,则会判断从开始CMSGC到现在经过的时间是否大于该参数的时间。若大于,则会进行一次CMSGC。
这里还需要说明的一点是-XX:CMSInitiatingOccupancyFraction配置的默认值是-1.当它是-1的时候,会通过concurrentMarkSweep.cpp中的init_initiating_occupancy(intx io, uintx tr)方法计算。
计算得到_initiating_occupancy=(100-40+80*40/100)/100=92%。也就是说,如果不配置CMSInitiatingOccupancyFraction参数,默认情况下,initiating_occupancy的值就是92%。
从零开始看源码,旨在从源码验证书上的结论,探索书上未知的细节。有疑问欢迎留言探讨
个人源码地址:https://github.com/FlashLightNing/openjdk-notes
还有一个openjdk6,7,8,9的地址:https://github.com/dmlloyd/openjdk
jvm源码阅读笔记[1]:如何触发一次CMS回收
jvm源码阅读笔记[2]:你不知道的晋升阈值TenuringThreshold详解
jvm源码阅读笔记[3]:从内存分配到触发GC的细节
jvm源码阅读笔记[4]:从GC说到vm operation
jvm源码阅读笔记[5]:内存分配失败触发的GC究竟对内存做了什么?
个人源码地址:https://github.com/FlashLightNing/openjdk-notes
还有一个openjdk6,7,8,9的地址:https://github.com/dmlloyd/openjdk
jvm源码阅读笔记[1]:如何触发一次CMS回收
jvm源码阅读笔记[2]:你不知道的晋升阈值TenuringThreshold详解
jvm源码阅读笔记[3]:从内存分配到触发GC的细节
jvm源码阅读笔记[4]:从GC说到vm operation
jvm源码阅读笔记[5]:内存分配失败触发的GC究竟对内存做了什么?
对于配置使用CMS回收器的应用,用jstack pid | grep GC 可以发现,有一个名为Concurrent Mark-Sweep GC Thread的线程。
这个线程的作用呢,就是每隔2秒钟,就检查一下老年代是否需要回收,如果需要,则就会进行一次CMS回收。为什么这么说呢?来看看这个线程对应的源码:源码地址
ConcurrentMarkSweepThread::ConcurrentMarkSweepThread(CMSCollector* collector) : ConcurrentGCThread() { assert(UseConcMarkSweepGC, "UseConcMarkSweepGC should be set");//表示在vm启动参数中必须加上UseConcMarkSweepGC assert(_cmst == NULL, "CMS thread already created"); _cmst = this; assert(_collector == NULL, "Collector already set"); _collector = collector; //前面的这些assert主要是一些参数判断,保证这些参数符合条件。 set_name("Concurrent Mark-Sweep GC Thread");//可以看到在这设置了线程名称 if (os::create_thread(this, os::cgc_thread)) { int native_prio; if (UseCriticalCMSThreadPriority) {//UseCriticalCMSThreadPriority默认false native_prio = os::java_to_os_priority[CriticalPriority]; } else { native_prio = os::java_to_os_priority[NearMaxPriority]; } os::set_native_priority(this, native_prio); if (!DisableStartThread) { os::start_thread(this); } } _sltMonitor = SLT_lock; assert(!CMSIncrementalMode || icms_is_enabled(), "Error"); }
再来看看比较关键的线程的run()方法:
//线程的run方法 void ConcurrentMarkSweepThread::run() { assert(this == cmst(), "just checking"); this->record_stack_base_and_size(); this->initialize_thread_local_storage(); this->set_active_handles(JNIHandleBlock::allocate_block()); // 检查是否为当前线程 assert(this == Thread::current(), "just checking"); if (BindCMSThreadToCPU && !os::bind_to_processor(CPUForCMSThread)) { warning("Couldn't bind CMS thread to processor " UINTX_FORMAT, CPUForCMSThread); } { CMSLoopCountWarn loopX("CMS::run", "waiting for " "Universe::is_fully_initialized()", 2); MutexLockerEx x(CGC_lock, true); set_CMS_flag(CMS_cms_wants_token); //阻塞到堆初始化完成且所有初始化工作都做完了 while (!is_init_completed() && !Universe::is_fully_initialized() && !_should_terminate) { CGC_lock->wait(true, 200); loopX.tick(); } CMSLoopCountWarn loopY("CMS::run", "waiting for SLT installation", 2); while (_slt == NULL && !_should_terminate) { CGC_lock->wait(true, 200); loopY.tick(); } clear_CMS_flag(CMS_cms_wants_token); } //_should_terminate变量只有在stop方法中才会为true,所以只要没有调用stop方法,这个就是while死循环,一直会检测 while (!_should_terminate) { sleepBeforeNextCycle();//该方法内部也是一个while循环,只有在_should_terminate=true或者是需要进行一次GC时才会结束该方法然后往下运行。具体看后面的分析 if (_should_terminate) break; //判断此次GC的类型 GCCause::Cause cause = _collector->_full_gc_requested ? _collector->_full_gc_cause : GCCause::_cms_concurrent_mark; _collector->collect_in_background(false, cause);//进行background回收 } assert(_should_terminate, "just checking"); verify_ok_to_terminate(); { MutexLockerEx mu(Terminator_lock, Mutex::_no_safepoint_check_flag); assert(_cmst == this, "Weird!"); _cmst = NULL; Terminator_lock->notify(); } // Thread destructor usually does this.. ThreadLocalStorage::set_thread(NULL); }
其中,关键的是调用的sleepBeforeNextCycle()方法:
void ConcurrentMarkSweepThread::sleepBeforeNextCycle() { while (!_should_terminate) { if (CMSIncrementalMode) {//CMS增量回收模式,默认false icms_wait(); if(CMSWaitDuration >= 0) { wait_on_cms_lock_for_scavenge(CMSWaitDuration); } return; } else { //CMSWaitDuration 默认是2000 if(CMSWaitDuration >= 0) { wait_on_cms_lock_for_scavenge(CMSWaitDuration);//该方法类似于j.u.c里面while循环+超时等待,会阻塞线程CMSWaitDuration毫秒时间 } else { wait_on_cms_lock(CMSCheckInterval); } } //根据一定条件判断是否需要进行一次CMSGC if (_collector->shouldConcurrentCollect()) { return; } } }
根据CMSWaitDuration参数,我们可以知道,该cms线程会一直在sleepBeforeNextCycle()方法内循环,然后阻塞完2000毫秒后,判断是否需要进行一次GC。如果不需要,继续走上面的流程。如果需要,就结束sleepBeforeNextCycle()方法,然后在run方法中,进行一次_collector->collect_in_background()调用,从而回收老年代。
需要注意的是,这里进行的是background式的CMS GC。还有一种是foreground式的CMS GC.具体它们的区别,可以参考这篇文章:http://lovestblog.cn/blog/2015/05/07/system-gc/
所以总结起来就是:当堆初始化完成后,就会创建一个cms的 gc线程。这个线程的作用,就是根据配置的CMSWaitDuration时间(默认2000ms),轮询判断老年代是否需要进行GC(根据一定的条件,比如占用率达到阈值)。如果需要GC,则进行一次background式的CMS GC。
那么,再来看看满足哪些条件的时候jvm会认为需要进行一次CMS GC呢?关键看看concurrentMarkSweepGeneration.cpp中的shouldConcurrentCollect()方法。(concurrentMarkSweepGeneration.cpp地址)
//方法开始:判断是否要进行并发回收 bool CMSCollector::shouldConcurrentCollect() { if (_full_gc_requested) {//发起了一次full gc时 if (Verbose && PrintGCDetails) { gclog_or_tty->print_cr("CMSCollector: collect because of explicit " " gc request (or gc_locker)"); } return true; } NOT_PRODUCT( if (RotateCMSCollectionTypes && (_cmsGen->debug_collection_type() != ConcurrentMarkSweepGeneration::Concurrent_collection_type)) { assert(_cmsGen->debug_collection_type() != ConcurrentMarkSweepGeneration::Unknown_collection_type, "Bad cms collection type"); return false; } ) FreelistLocker x(this); // Print out lots of information which affects the initiation of a collection. if (PrintCMSInitiationStatistics && stats().valid()) { gclog_or_tty->print("CMSCollector shouldConcurrentCollect: "); gclog_or_tty->stamp(); gclog_or_tty->cr(); stats().print_on(gclog_or_tty); gclog_or_tty->print_cr("time_until_cms_gen_full %3.7f", stats().time_until_cms_gen_full()); gclog_or_tty->print_cr("free="SIZE_FORMAT, _cmsGen->free()); gclog_or_tty->print_cr("contiguous_available="SIZE_FORMAT, _cmsGen->contiguous_available()); gclog_or_tty->print_cr("promotion_rate=%g", stats().promotion_rate()); gclog_or_tty->print_cr("cms_allocation_rate=%g", stats().cms_allocation_rate()); gclog_or_tty->print_cr("occupancy=%3.7f", _cmsGen->occupancy()); gclog_or_tty->print_cr("initiatingOccupancy=%3.7f", _cmsGen->initiating_occupancy()); gclog_or_tty->print_cr("cms_time_since_begin=%3.7f", stats().cms_time_since_begin()); gclog_or_tty->print_cr("cms_time_since_end=%3.7f", stats().cms_time_since_end()); gclog_or_tty->print_cr("metadata initialized %d", MetaspaceGC::should_concurrent_collect()); } /* UseCMSInitiatingOccupancyOnly默认false 如果并不是只有在占用率达到要求的时候才回收,则会动态进行计算 如果预计的完成CMS回收的所需要的时间小于预计的老年代填满的时间,则进行回收。 */ if (!UseCMSInitiatingOccupancyOnly) { if (stats().valid()) { if (stats().time_until_cms_start() == 0.0) { return true; } } else { if (_cmsGen->occupancy() >= _bootstrap_occupancy) {//_bootstrap_occupancy默认50% if (Verbose && PrintGCDetails) { gclog_or_tty->print_cr( " CMSCollector: collect for bootstrapping statistics:" " occupancy = %f, boot occupancy = %f", _cmsGen->occupancy(), _bootstrap_occupancy); } return true; } } } if (_cmsGen->should_concurrent_collect()) { if (Verbose && PrintGCDetails) { gclog_or_tty->print_cr("CMS old gen initiated"); } return true; } /* 如果认为一个增量式的回收可能失败,我们就会开始一次回收。这在实践中不太可能生效,因为这时候已经太迟了。 */ GenCollectedHeap* gch = GenCollectedHeap::heap(); assert(gch->collector_policy()->is_two_generation_policy(), "You may want to check the correctness of the following"); if (gch->incremental_collection_will_fail(true /* consult_young */)) { if (Verbose && PrintGCDetails) { gclog_or_tty->print("CMSCollector: collect because incremental collection will fail "); } return true; } if (MetaspaceGC::should_concurrent_collect()) {//检查metaspace是否需要GC if (Verbose && PrintGCDetails) { gclog_or_tty->print("CMSCollector: collect for metadata allocation "); } return true; } // CMSTriggerInterval starts a CMS cycle if enough time has passed. //CMS的触发间隔,默认-1. if (CMSTriggerInterval >= 0) { if (CMSTriggerInterval == 0) { // Trigger always return true; } if (stats().cms_time_since_begin() >= (CMSTriggerInterval / ((double) MILLIUNITS))) { if (Verbose && PrintGCDetails) { if (stats().valid()) { gclog_or_tty->print_cr("CMSCollector: collect because of trigger interval (time since last begin %3.7f secs)", stats().cms_time_since_begin()); } else { gclog_or_tty->print_cr("CMSCollector: collect because of trigger interval (first collection)"); } } return true; } } return false; } //方法结束
这个方法比较长,主要有以下几种方式触发:
(1) 请求进行一次fullgc,如调用System.gc时
(2) 当没有设置UseCMSInitiatingOccupancyOnly时,会动态计算。如果完成CMS回收的所需要的预计的时间小于预计的CMS回收的分代填满的时间,就进行回收
(3) 调用should_concurrent_collect()方法返回true(后面会讲都有哪些情况时该方法会返回true)
(4) 如果预计增量式回收会失败时,也会触发一次回收。
(5) 如果metaSpace认为需要回收metaSpace区域,也会触发一次cms回收
(6) 如果设置了CMSTriggerInterval参数。该参数默认值-1,表示CMS回收的间隔。如果=0表示一直触发CMSGC。如果>0,则会判断从开始CMSGC到现在经过的时间是否大于该参数的时间。若大于,则会进行一次CMSGC。
如果都不满足,则返回false,表示不满足进行GC的条件。
再来看看第3条,看看什么时候should_concurrent_collect()会返回true。
bool ConcurrentMarkSweepGeneration::should_concurrent_collect() const { assert_lock_strong(freelistLock()); if (occupancy() > initiating_occupancy()) { if (PrintGCDetails && Verbose) { gclog_or_tty->print(" %s: collect because of occupancy %f / %f ", short_name(), occupancy(), initiating_occupancy()); } return true; } /* 如果使用UseCMSInitiatingOccupancyOnly参数,表示只有在使用率超过配置的情况下的时候 才会进行CMS回收。 如果没有使用-XX:+UseCMSInitiatingOccupancyOnly, 那么HotSpot VM只是利用这个值来启动第一次CMS垃圾回收,后面都是使用HotSpot VM自动计算出来的值。 */ if (UseCMSInitiatingOccupancyOnly) { return false; } if (expansion_cause() == CMSExpansionCause::_satisfy_allocation) {//扩容 if (PrintGCDetails && Verbose) { gclog_or_tty->print(" %s: collect because expanded for allocation ", short_name()); } return true; } //这里的_cmsSpace是指CompactibleFreeListSpace if (_cmsSpace->should_concurrent_collect()) { if (PrintGCDetails && Verbose) { gclog_or_tty->print(" %s: collect because cmsSpace says so ", short_name()); } return true; } return false; }
可以发现,当
(1)堆当前的使用率>配置的initiating_occupancy,也就是我们经常配置的-XX:CMSInitiatingOccupancyFraction=80。
(2)当设置了UseCMSInitiatingOccupancyOnly=true,也就是只允许使用率达到阈值才进行回收时,不会判断其他条件。
(3)当堆扩容的原因是_satisfy_allocation时。
(4)CompactibleFreeListSpace认为需要回收时。
总结起来,就是下面这么多原因:
(1)请求进行一次fullgc,如调用System.gc时
(2)当没有设置UseCMSInitiatingOccupancyOnly时,会动态计算。如果完成CMS回收的所需要的预计的时间小于预计的CMS回收的分代填满的时间,就进行回收
(3.1)堆当前的使用率>配置的initiating_occupancy,也就是我们经常配置的-XX:CMSInitiatingOccupancyFraction=80。
(3.2)当设置了UseCMSInitiatingOccupancyOnly=true,也就是只允许使用率达到阈值才进行回收时,不会判断其他条件。
(3.3)当堆扩容的原因是_satisfy_allocation时。
(3.4)CompactibleFreeListSpace认为需要回收时。
(4)如果预计增量式回收会失败时,也会触发一次回收。
(5)如果metaSpace认为需要回收metaSpace区域,也会触发一次cms回收
(6)如果设置了CMSTriggerInterval参数。该参数默认值-1,表示CMS回收的间隔。如果=0表示一直触发CMSGC。如果>0,则会判断从开始CMSGC到现在经过的时间是否大于该参数的时间。若大于,则会进行一次CMSGC。
这里还需要说明的一点是-XX:CMSInitiatingOccupancyFraction配置的默认值是-1.当它是-1的时候,会通过concurrentMarkSweep.cpp中的init_initiating_occupancy(intx io, uintx tr)方法计算。
void ConcurrentMarkSweepGeneration::init_initiating_occupancy(intx io, uintx tr) { //tr为CMSTriggerRatio参数,表示触发CMS的比例,默认80%,。IO表示CMSInitiatingOccupancyFraction,即使用率,默认-1 assert(io <= 100 && tr <= 100, "Check the arguments"); if (io >= 0) { _initiating_occupancy = (double)io / 100.0; } else {//所以默认没有配置的情况下,会走下面的逻辑,MinHeapFreeRatio默认40 _initiating_occupancy = ((100 - MinHeapFreeRatio) + (double)(tr * MinHeapFreeRatio) / 100.0) / 100.0; } }
计算得到_initiating_occupancy=(100-40+80*40/100)/100=92%。也就是说,如果不配置CMSInitiatingOccupancyFraction参数,默认情况下,initiating_occupancy的值就是92%。
从零开始看源码,旨在从源码验证书上的结论,探索书上未知的细节。有疑问欢迎留言探讨
个人源码地址:https://github.com/FlashLightNing/openjdk-notes
还有一个openjdk6,7,8,9的地址:https://github.com/dmlloyd/openjdk
jvm源码阅读笔记[1]:如何触发一次CMS回收
jvm源码阅读笔记[2]:你不知道的晋升阈值TenuringThreshold详解
jvm源码阅读笔记[3]:从内存分配到触发GC的细节
jvm源码阅读笔记[4]:从GC说到vm operation
jvm源码阅读笔记[5]:内存分配失败触发的GC究竟对内存做了什么?
相关文章推荐
- jvm源码阅读笔记[3]:从内存分配到触发GC的细节
- jvm源码阅读笔记[5]:内存分配失败触发的GC究竟对内存做了什么?
- jvm源码阅读笔记[4]:从GC说到vm operation
- 如何阅读Vuejs源码、学习笔记
- OBS源码阅读笔记--如何修改录播文件路径到exe所在目录
- Three.js源码阅读笔记(物体是如何组织的)
- jvm源码阅读笔记[7]-从jstat -gccause命令谈到jvm中都有哪些GC cause
- OBS源码阅读笔记(七)--如何往工程中添加自己的界面
- Three.js源码阅读笔记(物体是如何组织的)
- jvm源码阅读笔记[2]:你不知道的晋升阈值TenuringThreshold详解
- OBS源码阅读笔记--如何在外部修改推流分辨率
- jvm源码阅读笔记[6]-杂谈JIT中对Exception做的优化
- OBS源码阅读笔记--如何去掉工作模式下中间的多余按钮
- 如何阅读RxJava2源码,以及阅读笔记
- OBS源码阅读笔记--如何给source添加展示画面的地方呢?
- Spring源码解析——如何阅读源码
- Tensorflow object detection API 源码阅读笔记:架构
- JVM快速阅读笔记
- api.js源码阅读学习笔记