概述
最近一直迷惑CMS GC觸發有那些情況,專門去研究了一下CMS Thread 的源碼。廢話不說了,讓我們開始探究之旅。
ConcurrentMarkSweepThread
在啟動JVM虛擬機時,進行各種初始化操作,其中就包括了GC線程的初始化,CMS GC線程初始化主要是通過 ConcurrentMarkSweepThread(簡稱CMSThread) 類,下面具體研究一下該類的構造函數,源碼地址:hotspot\src\share\vm\gc_implementation\concurrentMarkSweep\concurrentMarkSweepThread.cpp
ConcurrentMarkSweepThread::ConcurrentMarkSweepThread(CMSCollector* collector)
: ConcurrentGCThread() {
//UseConcMarkSweepGC為true
assert(UseConcMarkSweepGC, "UseConcMarkSweepGC should be set");
assert(_cmst == NULL, "CMS thread already created");
_cmst = this;
assert(_collector == NULL, "Collector already set");
_collector = collector;
//設置線程名字
set_name("Concurrent Mark-Sweep GC Thread");
if (os::create_thread(this, os::cgc_thread)) {
int native_prio;
//UseCriticalCMSThreadPriority默認為false,如果配置為true,VMThread 可能不會獲得CPU
if (UseCriticalCMSThreadPriority) {
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");
}
上面是 CMSThread 線程的初始化,主要是設置線程名稱以及線程的優先級等。
與JAVA的線程類似,在 run 方法中,定義了 CMSThread 線程的工作,接下來,我們分析CMSThread線程的 run 方法。
void ConcurrentMarkSweepThread::run() {
...............................省略....................................
// Wait until Universe::is_fully_initialized()
{
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();
}
//等待surrogate locker thread的執行
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);
}
while (!_should_terminate) {
//如果沒有觸發CMS GC,CMSThread線程阻塞
sleepBeforeNextCycle();
if (_should_terminate) break;
GCCause::Cause cause = _collector->_full_gc_requested ?
_collector->_full_gc_cause : GCCause::_cms_concurrent_mark;
_collector->collect_in_background(false, cause);
}
...............................省略....................................
在上面的代碼中,等待各種初始化操作的完成,然后通過while循環,檢測是否有觸發GC,當觸發GC時,調用 collect_in_background 進行GC操作。關于CMS的GC詳情以后進行分析,我們本次主要分析CMS GC觸發的原因。從上面的代碼中可以看出,在 sleepBeforeNextCycle 方法中檢查是否要進行GC。
void ConcurrentMarkSweepThread::sleepBeforeNextCycle() {
while (!_should_terminate) {
if (CMSIncrementalMode) {//CMS增量模式回收,默認為false
icms_wait();
return;
} else {
//類似與Java中的wait(2000),CMSWaitDuration默認2000ms
wait_on_cms_lock(CMSWaitDuration);
}
// 檢查是否要進行CMS GC
if (_collector->shouldConcurrentCollect()) {
return;
}
}
}
上面的代碼中,判斷CMSIncrementalMode是否為True,如果為true,執行 icms_wait 方法,如果為false,讓線程阻塞2000ms,當阻塞時間超時以后,調用shouldConcurrentCollect 方法檢查是否需要執行CMS GC。
CMS GC觸發的情況
在上面的小節中,我們分析到 CMSThread 調用 shouldConcurrentCollect 方法來判斷是否觸發CMS GC,下面我們分析一下 shouldConcurrentCollect 內部的具體實現,該方法篇幅過長,我們將將源碼分段講解,源碼地址:hotspot\src\share\vm\gc_implementation\concurrentMarkSweep\concurrentMarkSweepGeneration.cpp
第一部分
//有FullGC的請求
if (_full_gc_requested) {
if (Verbose && PrintGCDetails) {
gclog_or_tty->print_cr("CMSCollector: collect because of explicit "
" gc request (or gc_locker)");
}
return true;
}
如果有FullGC的請求,就觸發一次GC(比如調用System.gc()觸發GC)
第二部分
//UseCMSInitiatingOccupancyOnly默認false
//UseCMSInitiatingOccupancyOnly為false,會動態計算閾值
//預計完成CMS回收所需要的時間小于預計的老年代填滿的時間,則進行回收。
if (!UseCMSInitiatingOccupancyOnly) {
if (stats().valid()) {
if (stats().time_until_cms_start() == 0.0) {
return true;
}
} else {
//_bootstrap_occupancy默認50%
if (_cmsGen->occupancy() >= _bootstrap_occupancy) {
if (Verbose && PrintGCDetails) {
gclog_or_tty->print_cr(
" CMSCollector: collect for bootstrapping statistics:"
" occupancy = %f, boot occupancy = %f", _cmsGen->occupancy(),
_bootstrap_occupancy);
}
return true;
}
}
}
如果預計完成CMS回收所需要的時間小于預計的老年代填滿的時間,則進行回收。
第三部分
if (_cmsGen->should_concurrent_collect()) {
if (Verbose && PrintGCDetails) {
gclog_or_tty->print_cr("CMS old gen initiated");
}
return true;
}
在上面的代碼種可以看出調用 should_concurrent_collect 方法判斷,是否觸發GC,深入到該方法中,去了解具體的實現。
bool ConcurrentMarkSweepGeneration::should_concurrent_collect() const {
//內存使用率大于初始化參數
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 為true
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;
}
if (_cmsSpace->should_concurrent_collect()) {
if (PrintGCDetails && Verbose) {
gclog_or_tty->print(" %s: collect because cmsSpace says so ",
short_name());
}
return true;
}
return false;
}
在上面的代碼中,首先判斷老年代內存使用率是否大于初始化參數,如果為true,則觸發GC,如果為false,且UseCMSInitiatingOccupancyOnly 為true,則返回false。
接下來判斷是否在進行堆擴容,如果為true,則觸發GC,如果為false,則判斷CompactibleFreeListSpace是否需要進行GC。
第四部分
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;
}
在上面的代碼中,判斷年輕代存活的對象是否可能會失敗,如果失敗,觸發GC。查看 incremental_collection_will_fail 的具體實現,源碼地址:hotspot\src\share\vm\memory\genCollectedHeap.cpp
bool incremental_collection_will_fail(bool consult_young) {
assert(heap()->collector_policy()->is_two_generation_policy(),
"the following definition may not be suitable for an n(>2)-generation system");
return incremental_collection_failed() ||
(consult_young && !get_gen(0)->collection_attempt_is_safe());
}
在上面的代碼中,調用 incremental_collection_failed 方法判斷之前是否晉升失敗,默認為false,同時調用年輕代的 collection_attempt_is_safe 方法,判斷本次晉升是否失敗,根據或操作的結果判斷是否進行GC,深入查看 collection_attempt_is_safe
源碼,源碼地址:hotspot\src\share\vm\memory\defNewGeneration.cpp
bool DefNewGeneration::collection_attempt_is_safe() {
//to空間是否為空
if (!to()->is_empty()) {
if (Verbose && PrintGCDetails) {
gclog_or_tty->print(" :: to is not empty :: ");
}
return false;
}
//下一個內存管理器為空
if (_next_gen == NULL) {
GenCollectedHeap* gch = GenCollectedHeap::heap();
_next_gen = gch->next_gen(this);
assert(_next_gen != NULL,
"This must be the youngest gen, and not the only gen");
}
//判斷下一個內存管理器是否能夠安全晉升
return _next_gen->promotion_attempt_is_safe(used());
}
在上面的代碼中判斷TO空間是否為空,老年代是否存在,判斷老年代是否能夠安全晉升,繼續往下深入,源碼地址:hotspot\src\share\vm\gc_implementation\concurrentMarkSweep\concurrentMarkSweepGeneration.cpp
bool ConcurrentMarkSweepGeneration::promotion_attempt_is_safe(size_t max_promotion_in_bytes) const {
//最大可用空間
size_t available = max_available();
//之前晉升對象的平均大小
size_t av_promo = (size_t)gc_stats()->avg_promoted()->padded_average();
//允許的最大空間 > 平均晉升大小或者是允許最大空間 > 最大晉升大小
bool res = (available >= av_promo) || (available >= max_promotion_in_bytes);
if (Verbose && PrintGCDetails) {
gclog_or_tty->print_cr(
"CMS: promo attempt is%s safe: available("SIZE_FORMAT") %s av_promo("SIZE_FORMAT"),"
"max_promo("SIZE_FORMAT")",
res? "":" not", available, res? ">=":"<",
av_promo, max_promotion_in_bytes);
}
return res;
}
從上面的代碼中可以看出如果:允許的最大空間 > 平均晉升大小或者允許最大空間 > 最大晉升大小,那么晉升成功。
年輕代晉升失敗可能導致不斷進行CMS GC。
第五部分
// 主要判斷方法去是否需要繼續GC
if (CMSClassUnloadingEnabled && _permGen->should_concurrent_collect()) {
bool res = update_should_unload_classes();
if (res) {
if (Verbose && PrintGCDetails) {
gclog_or_tty->print_cr("CMS perm gen initiated");
}
return true;
}
}
在上面的代碼中判斷是否開啟了 CMSClassUnloadingEnabled 參數,然后調用 should_concurrent_collect 方法,(永久代的判斷和老年的方法是通用的)判斷是否需要進行GC。
總結
通過上面的分析,我們總結了一下情況可能會觸發 CMS GC
請求進行一次fullgc,如調用System.gc時
當沒有設置UseCMSInitiatingOccupancyOnly時,會動態計算。如果完成CMS回收的所需要的預計的時間小于預計的CMS回收的分代填滿的時間,就進行回收
調用should_concurrent_collect()方法返回true
如果預計增量式回收會失敗時,也會觸發一次回收。
如果metaSpace認為需要回收metaSpace區域,也會觸發一次cms回收
我們對(3)的情況在進行一下總結:
老年代使用率 > 配置的initiating_occupancy,我們啟動參數中會配置如下參數:-XX:CMSInitiatingOccupancyFraction=n
在啟動參數中配置了UseCMSInitiatingOccupancyOnly,如果空間使用率沒有達到閾值,直接返回。
堆擴容的原因是_satisfy_allocation
CompactibleFreeListSpace判斷需要進行回收
自我介紹
我是何勇,現在重慶豬八戒,多學學!!!