part 8
UseConcMarkSweepGC下的GC流程分析
相比于SerialGC,CMS要復雜得多,因為他是第一個GC線程可以和用戶線程并發執行的GC,GC線程和用戶線程并發執行這件事情是非常困難的,也是極其復雜的,因為垃圾收集的同時,用戶線程還在不斷的產生垃圾,或者改變引用關系,使得已經被GC線程標記為垃圾的對象活起來了,這些情況都需要CMS能夠很好的去解決;
CMS GC分為foreground gc和background gc,foreground gc是一種主動式GC,是Minor GC造成的一種FullGC,foreground gc將和Serial old使用同樣的垃圾收集算法來做FullGC(單線程,mark-sweep-compact);如果觸發了foreground gc,但是發現此時background gc正在工作,那么就會發生"Concurrent model fail";background gc也就是CMS old GC,只會收集老年代(ConcurrentMarkSweepGeneration),是一種周期性被動GC,ConcurrentMarkSweepThread會周期性的檢測是否需要觸發一次background gc,判斷條件一般是老年代空間使用超過了設置的觸發CMS old GC的閾值,默認為92%,可以通過CMSInitiatingOccupancyFraction來設置具體的值,建議開啟-XX:+UseCMSInitiatingOccupancyOnly,否則CMS會根據收集到的數據進行判斷,這樣可能情況就變得更加復雜了。
UseConcMarkSweepGC依然使用GenCollectedHeap作為堆管理器,所以GC策略還是和Serial GC一樣,這里就不再贅述,本文剩下的內容主要分析CMS Old GC的實現細節,以及background gc和foreground gc之間是如何相互配合來回收垃圾的。CMS過程復雜,下面是CMS Old GC可能經過的狀態枚舉:
// CMS abstract state machine
// initial_state: Idling
// next_state(Idling) = {Marking}
// next_state(Marking) = {Precleaning, Sweeping}
// next_state(Precleaning) = {AbortablePreclean, FinalMarking}
// next_state(AbortablePreclean) = {FinalMarking}
// next_state(FinalMarking) = {Sweeping}
// next_state(Sweeping) = {Resizing}
// next_state(Resizing) = {Resetting}
// next_state(Resetting) = {Idling}
// The numeric values below are chosen so that:
// . _collectorState <= Idling == post-sweep && pre-mark
// . _collectorState in (Idling, Sweeping) == {initial,final}marking ||
// precleaning || abortablePrecleanb
public:
enum CollectorState {
Resizing = 0,
Resetting = 1,
Idling = 2,
InitialMarking = 3,
Marking = 4,
Precleaning = 5,
AbortablePreclean = 6,
FinalMarking = 7,
Sweeping = 8
};
Idling狀態是初始狀態,也代表background gc目前不在進行垃圾收集,此時進行foreground gc是不會發生 "Concurrent mode fail"的,簡單說,CMS Old GC需要經過初始標記(STW)、并發標記、最終標記(STW)、清理垃圾這么幾個關鍵的步驟,看起來CMS Old GC的過程中一直在做標記的工作,這主要是CMS希望能盡量縮短暫停用戶線程的時候,所以有些階段就直接和用戶線程并發運行了,這就導致會產生“浮動垃圾”,使得CMS整體實現非常復雜難懂,下面按照一些關鍵步驟嘗試分析每一步所做的事情,以及每一步存在的意義以及可能存在的一些運行時表現。
CMSCollector::collect_in_background函數完成的工作就是background gc的工作,foreground gc的工作由CMSCollector::collect函數完成,下面的分析的入口均從這連個函數進入。
InitialMarking (初始標記)
初始標記是一個STW的過程,當CMS 發現當前狀態_collectorState為InitialMarking的時候就會執行初始化標記的工作,下面是InitialMarking工作的入口代碼:
case InitialMarking:
{
ReleaseForegroundGC x(this);
stats().record_cms_begin();
VM_CMS_Initial_Mark initial_mark_op(this);
VMThread::execute(&initial_mark_op);
}
// The collector state may be any legal state at this point
// since the background collector may have yielded to the
// foreground collector.
break;
VM_CMS_Initial_Mark的doit函數將被VMThread調度執行,下面來看看VM_CMS_Initial_Mark的doit函數的具體工作內容。
void VM_CMS_Initial_Mark::doit() {
HS_PRIVATE_CMS_INITMARK_BEGIN();
GCIdMark gc_id_mark(_gc_id);
_collector->_gc_timer_cm->register_gc_pause_start("Initial Mark");
GenCollectedHeap* gch = GenCollectedHeap::heap();
GCCauseSetter gccs(gch, GCCause::_cms_initial_mark);
VM_CMS_Operation::verify_before_gc();
IsGCActiveMark x; // stop-world GC active
_collector->do_CMS_operation(CMSCollector::CMS_op_checkpointRootsInitial, gch->gc_cause());
VM_CMS_Operation::verify_after_gc();
_collector->_gc_timer_cm->register_gc_pause_end();
HS_PRIVATE_CMS_INITMARK_END();
}
_collector->do_CMS_operation將被執行,看參數中CMSCollector::CMS_op_checkpointRootsInitial可知接下來會進行初始化標記的過程,CMSCollector::do_CMS_operation函數內容如下:
void CMSCollector::do_CMS_operation(CMS_op_type op, GCCause::Cause gc_cause) {
GCTraceCPUTime tcpu;
TraceCollectorStats tcs(counters());
switch (op) {
case CMS_op_checkpointRootsInitial: {
GCTraceTime(Info, gc) t("Pause Initial Mark", NULL, GCCause::_no_gc, true);
SvcGCMarker sgcm(SvcGCMarker::OTHER);
checkpointRootsInitial();
break;
}
case CMS_op_checkpointRootsFinal: {
GCTraceTime(Info, gc) t("Pause Remark", NULL, GCCause::_no_gc, true);
SvcGCMarker sgcm(SvcGCMarker::OTHER);
checkpointRootsFinal();
break;
}
default:
fatal("No such CMS_op");
}
}
這個函數在FinalMarking階段也會被調用,對應的Operation就是CMS_op_checkpointRootsFinal,無論是CMS_op_checkpointRootsFinal還是CMS_op_checkpointRootsInitial都是STW的,現在來看看CMS_op_checkpointRootsInitial對應的流程;checkpointRootsInitial函數將被調用:
// Checkpoint the roots into this generation from outside
// this generation. [Note this initial checkpoint need only
// be approximate -- we'll do a catch up phase subsequently.]
void CMSCollector::checkpointRootsInitial() {
assert(_collectorState == InitialMarking, "Wrong collector state");
check_correct_thread_executing();
TraceCMSMemoryManagerStats tms(_collectorState,GenCollectedHeap::heap()->gc_cause());
save_heap_summary();
report_heap_summary(GCWhen::BeforeGC);
ReferenceProcessor* rp = ref_processor();
assert(_restart_addr == NULL, "Control point invariant");
{
// acquire locks for subsequent manipulations
MutexLockerEx x(bitMapLock(),
Mutex::_no_safepoint_check_flag);
checkpointRootsInitialWork();
// enable ("weak") refs discovery
rp->enable_discovery();
_collectorState = Marking;
}
}
checkpointRootsInitialWork是需要重點關注的函數調用;CMSParallelInitialMarkEnabled默認是true的,所以將會執行下面這段代碼:
// The parallel version.
WorkGang* workers = gch->workers();
assert(workers != NULL, "Need parallel worker threads.");
uint n_workers = workers->active_workers();
StrongRootsScope srs(n_workers);
CMSParInitialMarkTask tsk(this, &srs, n_workers);
initialize_sequential_subtasks_for_young_gen_rescan(n_workers);
// If the total workers is greater than 1, then multiple workers
// may be used at some time and the initialization has been set
// such that the single threaded path cannot be used.
if (workers->total_workers() > 1) {
workers->run_task(&tsk);
} else {
tsk.work(0);
}
CMSParInitialMarkTask就是具體的任務,CMSParInitialMarkTask::work將完成具體的InitialMarking工作,下面是CMSParInitialMarkTask::work的具體細節,從圖中的代碼片段可以看出來InitialMarking需要完成的工作是哪些:
void CMSParInitialMarkTask::work(uint worker_id) {
elapsedTimer _timer;
ResourceMark rm;
HandleMark hm;
// ---------- scan from roots --------------
_timer.start();
GenCollectedHeap* gch = GenCollectedHeap::heap();
ParMarkRefsIntoClosure par_mri_cl(_collector->_span, &(_collector->_markBitMap));
// ---------- young gen roots --------------
{
work_on_young_gen_roots(&par_mri_cl);
_timer.stop();
log_trace(gc, task)("Finished young gen initial mark scan work in %dth thread: %3.3f sec",
worker_id, _timer.seconds());
}
// ---------- remaining roots --------------
_timer.reset();
_timer.start();
CLDToOopClosure cld_closure(&par_mri_cl, true);
gch->cms_process_roots(_strong_roots_scope,
false, // yg was scanned above
GenCollectedHeap::ScanningOption(_collector->CMSCollector::roots_scanning_options()),
_collector->should_unload_classes(),
&par_mri_cl,
&cld_closure);
assert(_collector->should_unload_classes()
|| (_collector->CMSCollector::roots_scanning_options() & GenCollectedHeap::SO_AllCodeCache),
"if we didn't scan the code cache, we have to be ready to drop nmethods with expired weak oops");
_timer.stop();
log_trace(gc, task)("Finished remaining root initial mark scan work in %dth thread: %3.3f sec",
worker_id, _timer.seconds());
}
InitialMarking階段將以GCRoot和新生代對象為Root掃描老年代,來標記出老年代存活的對象;在具體實現上,CMS使用稱為“三色標記”的算法來進行存活對象標記,白色代表沒有被標記,灰色代表自身被標記,但是引用的對象還沒有被標記,黑色代表自身被標記,并且引用的對象也已經標記物完成,具體的算法實現非常復雜,本文就不繼續分析研究了。
Marking (并發標記)
該階段稱為并發標記,這里的并發,指的是用戶線程和GC線程并發執行,介于這種并發執行的情況,可能在GC線程標記的過程中存在新生代對象晉升的情況,或者根據內存分配策略大對象直接在老年代分配空間,以及Minor GC的時候存活對象無法轉移到To Survivor中去而提前晉升轉移到老年代中來,或者更為復雜的是對象引用關系發生變化,這些對象都需要被重新標記,否則就會錯誤的以為這部分對象不可達而被清理,造成嚴重的運行時錯誤。
case Marking:
// initial marking in checkpointRootsInitialWork has been completed
if (markFromRoots()) { // we were successful
assert(_collectorState == Precleaning, "Collector state should "
"have changed");
} else {
assert(_foregroundGCIsActive, "Internal state inconsistency");
}
break;
markFromRoots函數將負責并發標記階段的全部工作,下面來分析一下這個階段的主要流程;
bool CMSCollector::markFromRoots() {
// we might be tempted to assert that:
// assert(!SafepointSynchronize::is_at_safepoint(),
// "inconsistent argument?");
// However that wouldn't be right, because it's possible that
// a safepoint is indeed in progress as a young generation
// stop-the-world GC happens even as we mark in this generation.
assert(_collectorState == Marking, "inconsistent state?");
check_correct_thread_executing();
verify_overflow_empty();
// Weak ref discovery note: We may be discovering weak
// refs in this generation concurrent (but interleaved) with
// weak ref discovery by the young generation collector.
CMSTokenSyncWithLocks ts(true, bitMapLock());
GCTraceCPUTime tcpu;
CMSPhaseAccounting pa(this, "Concurrent Mark");
bool res = markFromRootsWork();
if (res) {
_collectorState = Precleaning;
} else { // We failed and a foreground collection wants to take over
assert(_foregroundGCIsActive, "internal state inconsistency");
assert(_restart_addr == NULL, "foreground will restart from scratch");
log_debug(gc)("bailing out to foreground collection");
}
verify_overflow_empty();
return res;
}
markFromRoots函數中的markFromRootsWork函數調用將完成主要的工作,然后判斷該階段的任務是否成功執行,如果是的話,那么就轉移狀態到Precleaning,接著GCThread就會進行下一階段Precleaning的工作;下面來看看markFromRootsWork函數實現的細節:
bool CMSCollector::markFromRootsWork() {
// iterate over marked bits in bit map, doing a full scan and mark
// from these roots using the following algorithm:
// . if oop is to the right of the current scan pointer,
// mark corresponding bit (we'll process it later)
// . else (oop is to left of current scan pointer)
// push oop on marking stack
// . drain the marking stack
// Note that when we do a marking step we need to hold the
// bit map lock -- recall that direct allocation (by mutators)
// and promotion (by the young generation collector) is also
// marking the bit map. [the so-called allocate live policy.]
// Because the implementation of bit map marking is not
// robust wrt simultaneous marking of bits in the same word,
// we need to make sure that there is no such interference
// between concurrent such updates.
// already have locks
assert_lock_strong(bitMapLock());
verify_work_stacks_empty();
verify_overflow_empty();
bool result = false;
if (CMSConcurrentMTEnabled && ConcGCThreads > 0) {
result = do_marking_mt();
} else {
result = do_marking_st();
}
return result;
}
如果設置了CMSConcurrentMTEnabled,并且ConcGCThreads數量大于0,那么就會執行do_marking_mt,也就是多線程版本,否則就會執行do_marking_st,也就是單線程版本;為了分析簡單,下面只分析單線程版本的內容:
bool CMSCollector::do_marking_st() {
ResourceMark rm;
HandleMark hm;
// Temporarily make refs discovery single threaded (non-MT)
ReferenceProcessorMTDiscoveryMutator rp_mut_discovery(ref_processor(), false);
MarkFromRootsClosure markFromRootsClosure(this, _span, &_markBitMap,
&_markStack, CMSYield);
// the last argument to iterate indicates whether the iteration
// should be incremental with periodic yields.
_markBitMap.iterate(&markFromRootsClosure);
// If _restart_addr is non-NULL, a marking stack overflow
// occurred; we need to do a fresh iteration from the
// indicated restart address.
while (_restart_addr != NULL) {
if (_foregroundGCIsActive) {
// We may be running into repeated stack overflows, having
// reached the limit of the stack size, while making very
// slow forward progress. It may be best to bail out and
// let the foreground collector do its job.
// Clear _restart_addr, so that foreground GC
// works from scratch. This avoids the headache of
// a "rescan" which would otherwise be needed because
// of the dirty mod union table & card table.
_restart_addr = NULL;
return false; // indicating failure to complete marking
}
// Deal with stack overflow:
// we restart marking from _restart_addr
HeapWord* ra = _restart_addr;
markFromRootsClosure.reset(ra);
_restart_addr = NULL;
_markBitMap.iterate(&markFromRootsClosure, ra, _span.end());
}
return true;
}
markFromRootsClosure是一個閉包函數對象,它里面的do_bit函數將會被BitMap::iterate來調用,調用關系可以在CMSCollector::do_marking_st函數中看到,先開看看BitMap::iterate的實現:
// Note that if the closure itself modifies the bitmap
// then modifications in and to the left of the _bit_ being
// currently sampled will not be seen. Note also that the
// interval [leftOffset, rightOffset) is right open.
bool BitMap::iterate(BitMapClosure* blk, idx_t leftOffset, idx_t rightOffset) {
verify_range(leftOffset, rightOffset);
idx_t startIndex = word_index(leftOffset);
idx_t endIndex = MIN2(word_index(rightOffset) + 1, size_in_words());
for (idx_t index = startIndex, offset = leftOffset;
offset < rightOffset && index < endIndex;
offset = (++index) << LogBitsPerWord) {
idx_t rest = map(index) >> (offset & (BitsPerWord - 1));
for (; offset < rightOffset && rest != 0; offset++) {
if (rest & 1) {
if (!blk->do_bit(offset)) return false;
// resample at each closure application
// (see, for instance, CMS bug 4525989)
rest = map(index) >> (offset & (BitsPerWord -1));
}
rest = rest >> 1;
}
}
return true;
}
可以看到不斷的調用了BitMapClosure的do_bit函數,這里的BitMapClosure就是MarkFromRootsClosure;下面來看看do_bit的具體實現:
bool MarkFromRootsClosure::do_bit(size_t offset) {
if (_skipBits > 0) {
_skipBits--;
return true;
}
// convert offset into a HeapWord*
HeapWord* addr = _bitMap->startWord() + offset;
assert(_bitMap->endWord() && addr < _bitMap->endWord(),
"address out of range");
assert(_bitMap->isMarked(addr), "tautology");
if (_bitMap->isMarked(addr+1)) {
// this is an allocated but not yet initialized object
assert(_skipBits == 0, "tautology");
_skipBits = 2; // skip next two marked bits ("Printezis-marks")
oop p = oop(addr);
if (p->klass_or_null_acquire() == NULL) {
DEBUG_ONLY(if (!_verifying) {)
// We re-dirty the cards on which this object lies and increase
// the _threshold so that we'll come back to scan this object
// during the preclean or remark phase. (CMSCleanOnEnter)
if (CMSCleanOnEnter) {
size_t sz = _collector->block_size_using_printezis_bits(addr);
HeapWord* end_card_addr = (HeapWord*)round_to(
(intptr_t)(addr+sz), CardTableModRefBS::card_size);
MemRegion redirty_range = MemRegion(addr, end_card_addr);
assert(!redirty_range.is_empty(), "Arithmetical tautology");
// Bump _threshold to end_card_addr; note that
// _threshold cannot possibly exceed end_card_addr, anyhow.
// This prevents future clearing of the card as the scan proceeds
// to the right.
assert(_threshold <= end_card_addr,
"Because we are just scanning into this object");
if (_threshold < end_card_addr) {
_threshold = end_card_addr;
}
if (p->klass_or_null_acquire() != NULL) {
// Redirty the range of cards...
_mut->mark_range(redirty_range);
} // ...else the setting of klass will dirty the card anyway.
}
DEBUG_ONLY(})
return true;
}
}
scanOopsInOop(addr);
return true;
}
主要關系MarkFromRootsClosure::scanOopsInOop函數:
void MarkFromRootsClosure::scanOopsInOop(HeapWord* ptr) {
assert(_bitMap->isMarked(ptr), "expected bit to be set");
assert(_markStack->isEmpty(),
"should drain stack to limit stack usage");
// convert ptr to an oop preparatory to scanning
oop obj = oop(ptr);
// Ignore mark word in verification below, since we
// may be running concurrent with mutators.
assert(obj->is_oop(true), "should be an oop");
assert(_finger <= ptr, "_finger runneth ahead");
// advance the finger to right end of this object
_finger = ptr + obj->size();
assert(_finger > ptr, "we just incremented it above");
// On large heaps, it may take us some time to get through
// the marking phase. During
// this time it's possible that a lot of mutations have
// accumulated in the card table and the mod union table --
// these mutation records are redundant until we have
// actually traced into the corresponding card.
// Here, we check whether advancing the finger would make
// us cross into a new card, and if so clear corresponding
// cards in the MUT (preclean them in the card-table in the
// future).
DEBUG_ONLY(if (!_verifying) {)
// The clean-on-enter optimization is disabled by default,
// until we fix 6178663.
if (CMSCleanOnEnter && (_finger > _threshold)) {
// [_threshold, _finger) represents the interval
// of cards to be cleared in MUT (or precleaned in card table).
// The set of cards to be cleared is all those that overlap
// with the interval [_threshold, _finger); note that
// _threshold is always kept card-aligned but _finger isn't
// always card-aligned.
HeapWord* old_threshold = _threshold;
assert(old_threshold == (HeapWord*)round_to(
(intptr_t)old_threshold, CardTableModRefBS::card_size),
"_threshold should always be card-aligned");
_threshold = (HeapWord*)round_to(
(intptr_t)_finger, CardTableModRefBS::card_size);
MemRegion mr(old_threshold, _threshold);
assert(!mr.is_empty(), "Control point invariant");
assert(_span.contains(mr), "Should clear within span");
_mut->clear_range(mr);
}
DEBUG_ONLY(})
// Note: the finger doesn't advance while we drain
// the stack below.
PushOrMarkClosure pushOrMarkClosure(_collector,
_span, _bitMap, _markStack,
_finger, this);
bool res = _markStack->push(obj);
assert(res, "Empty non-zero size stack should have space for single push");
while (!_markStack->isEmpty()) {
oop new_oop = _markStack->pop();
// Skip verifying header mark word below because we are
// running concurrent with mutators.
assert(new_oop->is_oop(true), "Oops! expected to pop an oop");
// now scan this oop's oops
new_oop->oop_iterate(&pushOrMarkClosure);
do_yield_check();
}
assert(_markStack->isEmpty(), "tautology, emphasizing post-condition");
}
看到oop_iterate,就是進行對象標記工作了,當然,具體的工作還是由PushOrMarkClosure的閉包函數do_oop完成的,下面來看看實現細節:
void PushOrMarkClosure::do_oop(oop obj) {
// Ignore mark word because we are running concurrent with mutators.
assert(obj->is_oop_or_null(true), "Expected an oop or NULL at " PTR_FORMAT, p2i(obj));
HeapWord* addr = (HeapWord*)obj;
if (_span.contains(addr) && !_bitMap->isMarked(addr)) {
// Oop lies in _span and isn't yet grey or black
_bitMap->mark(addr); // now grey
if (addr < _finger) {
// the bit map iteration has already either passed, or
// sampled, this bit in the bit map; we'll need to
// use the marking stack to scan this oop's oops.
bool simulate_overflow = false;
NOT_PRODUCT(
if (CMSMarkStackOverflowALot &&
_collector->simulate_overflow()) {
// simulate a stack overflow
simulate_overflow = true;
}
)
if (simulate_overflow || !_markStack->push(obj)) { // stack overflow
log_trace(gc)("CMS marking stack overflow (benign) at " SIZE_FORMAT, _markStack->capacity());
assert(simulate_overflow || _markStack->isFull(), "Else push should have succeeded");
handle_stack_overflow(addr);
}
}
// anything including and to the right of _finger
// will be scanned as we iterate over the remainder of the
// bit map
do_yield_check();
}
}
可以看到,do_oop函數會將對象標記,并且將對象push到_markStack中去,然后在MarkFromRootsClosure::scanOopsInOop的while循環中將從_markStack中pop出一個obj繼續遍歷標記,整個過程是類似于遞歸完成的;所以并發標記階段完成的工作就是根據初始化標記階段標記出來的對象為Root,遞歸標記這些root可達的引用,只是在標記的過程中用戶線程也是并發執行的,所以情況就會比較復雜,這也是為什么CMS需要有多次標記動作的原因,如果不執行多次標記,那么就可能會將一些存活的對象漏標記了,那么清理的時候就會誤清理。
Precleaning (預清理)
通過Marking之后,_collectorState就會被更新為Precleaning,該階段的入口如下:
case Precleaning:
// marking from roots in markFromRoots has been completed
preclean();
assert(_collectorState == AbortablePreclean ||
_collectorState == FinalMarking,
"Collector state should have changed");
break;
preclean函數就完成Precleaning階段的工作;
void CMSCollector::preclean() {
check_correct_thread_executing();
assert(Thread::current()->is_ConcurrentGC_thread(), "Wrong thread");
verify_work_stacks_empty();
verify_overflow_empty();
_abort_preclean = false;
if (CMSPrecleaningEnabled) {
if (!CMSEdenChunksRecordAlways) {
_eden_chunk_index = 0;
}
size_t used = get_eden_used();
size_t capacity = get_eden_capacity();
// Don't start sampling unless we will get sufficiently
// many samples.
if (used < (((capacity / CMSScheduleRemarkSamplingRatio) / 100)
* CMSScheduleRemarkEdenPenetration)) {
_start_sampling = true;
} else {
_start_sampling = false;
}
GCTraceCPUTime tcpu;
CMSPhaseAccounting pa(this, "Concurrent Preclean");
preclean_work(CMSPrecleanRefLists1, CMSPrecleanSurvivors1);
}
CMSTokenSync x(true); // is cms thread
if (CMSPrecleaningEnabled) {
sample_eden();
_collectorState = AbortablePreclean;
} else {
_collectorState = FinalMarking;
}
verify_work_stacks_empty();
verify_overflow_empty();
}
CMSPrecleaningEnabled用于控制是否進行Precleaning階段,CMSPrecleaningEnabled默認是true的,也就是默認會進行CMSPrecleaningEnabled,除非特殊情況,應該使用默認配置;preclean_work函數用于完成Precleaning的具體工作,Precleaning階段需要完成的工作包括:
- (1)、在并發標記階段,新生代引用了老年代對象,這些老年代對象需要被標記出來,防止被清理;
- (2)、在并發標記階段,老年代內部引用關系改變,這些老年代對象也需要被標記出來;
AbortablePreclean
AbortablePreclean其實是一個為了達到CMS的終極目標(縮短STW時間)而存在的,AbortablePreclean階段要做的工作和Precleaning相似,并且是一個循環的過程,但是是有條件的,達到某些條件之后就會跳出循環,執行STW的Final Mark階段,AbortablePreclean階段(包括Precleaning階段)所要做的事情就是盡最大努力減少Final Mark需要標記的對象,這樣STW的時間就減下來了。
abortable_preclean函數將負責完成AbortablePreclean階段的工作;
// Try and schedule the remark such that young gen
// occupancy is CMSScheduleRemarkEdenPenetration %.
void CMSCollector::abortable_preclean() {
check_correct_thread_executing();
assert(CMSPrecleaningEnabled, "Inconsistent control state");
assert(_collectorState == AbortablePreclean, "Inconsistent control state");
// If Eden's current occupancy is below this threshold,
// immediately schedule the remark; else preclean
// past the next scavenge in an effort to
// schedule the pause as described above. By choosing
// CMSScheduleRemarkEdenSizeThreshold >= max eden size
// we will never do an actual abortable preclean cycle.
if (get_eden_used() > CMSScheduleRemarkEdenSizeThreshold) {
GCTraceCPUTime tcpu;
CMSPhaseAccounting pa(this, "Concurrent Abortable Preclean");
// We need more smarts in the abortable preclean
// loop below to deal with cases where allocation
// in young gen is very very slow, and our precleaning
// is running a losing race against a horde of
// mutators intent on flooding us with CMS updates
// (dirty cards).
// One, admittedly dumb, strategy is to give up
// after a certain number of abortable precleaning loops
// or after a certain maximum time. We want to make
// this smarter in the next iteration.
// XXX FIX ME!!! YSR
size_t loops = 0, workdone = 0, cumworkdone = 0, waited = 0;
while (!(should_abort_preclean() ||
ConcurrentMarkSweepThread::cmst()->should_terminate())) {
workdone = preclean_work(CMSPrecleanRefLists2, CMSPrecleanSurvivors2);
cumworkdone += workdone;
loops++;
// Voluntarily terminate abortable preclean phase if we have
// been at it for too long.
if ((CMSMaxAbortablePrecleanLoops != 0) &&
loops >= CMSMaxAbortablePrecleanLoops) {
log_debug(gc)(" CMS: abort preclean due to loops ");
break;
}
if (pa.wallclock_millis() > CMSMaxAbortablePrecleanTime) {
log_debug(gc)(" CMS: abort preclean due to time ");
break;
}
// If we are doing little work each iteration, we should
// take a short break.
if (workdone < CMSAbortablePrecleanMinWorkPerIteration) {
// Sleep for some time, waiting for work to accumulate
stopTimer();
cmsThread()->wait_on_cms_lock(CMSAbortablePrecleanWaitMillis);
startTimer();
waited++;
}
}
log_trace(gc)(" [" SIZE_FORMAT " iterations, " SIZE_FORMAT " waits, " SIZE_FORMAT " cards)] ",
loops, waited, cumworkdone);
}
CMSTokenSync x(true); // is cms thread
if (_collectorState != Idling) {
assert(_collectorState == AbortablePreclean,
"Spontaneous state transition?");
_collectorState = FinalMarking;
} // Else, a foreground collection completed this CMS cycle.
return;
}
CMSScheduleRemarkEdenSizeThreshold默認值為2M,只有當Eden區域的使用量大于該值的時候才會進行接下來的工作;接下來看到的while循環里面做的工作和Precleaning是一樣的,因為和Precleaning階段一樣使用了preclean_work函數來完成具體的工作;這個while循環執行下去的條件值得分析一下;
- (1)、首先,CMSMaxAbortablePrecleanLoops用來設置最大的執行次數,默認是0,也就是不做限制
- (2)、CMSMaxAbortablePrecleanTime用于設置最大的循環時間,默認是5000ms
- (3)、如果每次循環花費的時間小于CMSAbortablePrecleanMinWorkPerIteration,那么就得等待CMSAbortablePrecleanWaitMillis再繼續循環,兩個值默認都是100ms
- (4)、should_abort_preclean函數判斷為true
inline bool CMSCollector::should_abort_preclean() const {
// We are in the midst of an "abortable preclean" and either
// scavenge is done or foreground GC wants to take over collection
return _collectorState == AbortablePreclean &&
(_abort_preclean || _foregroundGCIsActive ||
GenCollectedHeap::heap()->incremental_collection_will_fail(true /* consult_young */));
}
_foregroundGCIsActive代表正在進行Serial Old GC,incremental_collection_will_fail代表已經發生了"Promotion Fail",那么就不用進行“遞增式GC了”,也就是JVM建議直接進行FullGC,這些情況下should_abort_preclean都會返回true;
- (5)、ConcurrentMarkSweepThread::cmst()->should_terminate()返回true,代表ConcurrentMarkSweepThread被標記為需要terminate;
FinalMarking (最終標記)
FinalMarking屬于ReMark,需要STW,下面來分析一下這個階段需要完成的工作;首先大概猜測一下會進行哪些工作;首先,ReMark階段需要將最終要清理掉的對象標記出來,也就是這個階段完成之后,被標記為"垃圾"的對象將會在稍后的階段回收內存,初始標記階段完成了從GCRoot和新生代可達的老年代對象,兩個preclean階段是一種修正手段,將那些在GC線程和用戶線程并發執行時發生的變化記錄起來,并且因為FinalMark階段是STW的去掃描整個新生代來發現那些可達的老年代對象的,所以,新生代存活的對象如果很多的話,需要掃描的對象就很多,整個社會STW的時間就會上升,所以AbortablePreclean階段將盡力使得新生代發生一次YGC,這樣FinalMark時需要掃描的新生代對象就變少了。因為并發標記階段GC線程和用戶線程并發運行,所以可能會發生下列情況:
- (1)、并發期間新生代對象引用(或者解除引用)了老年代對象
- (2)、并發期間GCRoot引用(或者解除引用)了老年代對象
- (3)、并發期間老年代內部引用關系發生了變化(DirtyCard,引用關系改變的都將記錄在DirtyCard內,所以掃描DirtyCard即可)
這些情況FinalMark階段需要全部考慮到,下面具體來看看該階段完成的工作;
{
ReleaseForegroundGC x(this);
VM_CMS_Final_Remark final_remark_op(this);
VMThread::execute(&final_remark_op);
}
assert(_foregroundGCShouldWait, "block post-condition");
break;c
VM_CMS_Final_Remark類型的任務將被添加到VMThread里面執行,所以直接來看VM_CMS_Final_Remark的doit函數實現就可以知道具體的工作內容了;
void VM_CMS_Final_Remark::doit() {
if (lost_race()) {
// Nothing to do.
return;
}
HS_PRIVATE_CMS_REMARK_BEGIN();
GCIdMark gc_id_mark(_gc_id);
_collector->_gc_timer_cm->register_gc_pause_start("Final Mark");
GenCollectedHeap* gch = GenCollectedHeap::heap();
GCCauseSetter gccs(gch, GCCause::_cms_final_remark);
VM_CMS_Operation::verify_before_gc();
IsGCActiveMark x; // stop-world GC active
_collector->do_CMS_operation(CMSCollector::CMS_op_checkpointRootsFinal, gch->gc_cause());
VM_CMS_Operation::verify_after_gc();
_collector->save_heap_summary();
_collector->_gc_timer_cm->register_gc_pause_end();
HS_PRIVATE_CMS_REMARK_END();
}
和初始化標記一樣使用了do_CMS_operation函數,但是執行類型變為了CMSCollector::CMS_op_checkpointRootsFinal,下面看看do_CMS_operation內部執行CMSCollector::CMS_op_checkpointRootsFinal的那部分代碼;
void CMSCollector::checkpointRootsFinal() {
assert(_collectorState == FinalMarking, "incorrect state transition?");
check_correct_thread_executing();
// world is stopped at this checkpoint
assert(SafepointSynchronize::is_at_safepoint(),
"world should be stopped");
TraceCMSMemoryManagerStats tms(_collectorState,GenCollectedHeap::heap()->gc_cause());
verify_work_stacks_empty();
verify_overflow_empty();
log_debug(gc)("YG occupancy: " SIZE_FORMAT " K (" SIZE_FORMAT " K)",
_young_gen->used() / K, _young_gen->capacity() / K);
{
if (CMSScavengeBeforeRemark) {
GenCollectedHeap* gch = GenCollectedHeap::heap();
// Temporarily set flag to false, GCH->do_collection will
// expect it to be false and set to true
FlagSetting fl(gch->_is_gc_active, false);
gch->do_collection(true, // full (i.e. force, see below)
false, // !clear_all_soft_refs
0, // size
false, // is_tlab
GenCollectedHeap::YoungGen // type
);
}
FreelistLocker x(this);
MutexLockerEx y(bitMapLock(),
Mutex::_no_safepoint_check_flag);
checkpointRootsFinalWork();
}
verify_work_stacks_empty();
verify_overflow_empty();
}
如果設置了CMSScavengeBeforeRemark,那么就在執行FinalMark之前執行一次YGC,具體原因前面說過,因為FinalMark階段是STW的,如果新生代存活對象很多的話,就需要掃描很多對象,這個STW時間就上來了,所以提前進行一次YGC,那么就可以讓新生代中廢棄的對象回收掉,使得FinalMark階段掃描的對象減少;CMSScavengeBeforeRemark默認是false的,這個參數還是建議不要輕易設置,因為有preclean階段的存在,可能在preclean階段已經發生了一次YGC,如果再進行一次YGC,是沒有必要的,所以讓CMS自己去按照自己的節奏去工作,除非特別不否和預期的時候才去干涉他的執行。
Sweeping (清除)
就像名字一樣,該階段就是進行垃圾對象清理的,這個階段是并發的,整個CMS周期性GC過程中,除了initMark和FinalMark之外,其他階段都是可以并發的;sweep函數將完成清理的工作,在sweep函數內部調用了一個關鍵的函數sweepWork,下面是sweepWork的具體實現:
void CMSCollector::sweepWork(ConcurrentMarkSweepGeneration* old_gen) {
// We iterate over the space(s) underlying this generation,
// checking the mark bit map to see if the bits corresponding
// to specific blocks are marked or not. Blocks that are
// marked are live and are not swept up. All remaining blocks
// are swept up, with coalescing on-the-fly as we sweep up
// contiguous free and/or garbage blocks:
// We need to ensure that the sweeper synchronizes with allocators
// and stop-the-world collectors. In particular, the following
// locks are used:
// . CMS token: if this is held, a stop the world collection cannot occur
// . freelistLock: if this is held no allocation can occur from this
// generation by another thread
// . bitMapLock: if this is held, no other thread can access or update
//
// Note that we need to hold the freelistLock if we use
// block iterate below; else the iterator might go awry if
// a mutator (or promotion) causes block contents to change
// (for instance if the allocator divvies up a block).
// If we hold the free list lock, for all practical purposes
// young generation GC's can't occur (they'll usually need to
// promote), so we might as well prevent all young generation
// GC's while we do a sweeping step. For the same reason, we might
// as well take the bit map lock for the entire duration
// check that we hold the requisite locks
assert(have_cms_token(), "Should hold cms token");
assert(ConcurrentMarkSweepThread::cms_thread_has_cms_token(), "Should possess CMS token to sweep");
assert_lock_strong(old_gen->freelistLock());
assert_lock_strong(bitMapLock());
assert(!_inter_sweep_timer.is_active(), "Was switched off in an outer context");
assert(_intra_sweep_timer.is_active(), "Was switched on in an outer context");
old_gen->cmsSpace()->beginSweepFLCensus((float)(_inter_sweep_timer.seconds()),
_inter_sweep_estimate.padded_average(),
_intra_sweep_estimate.padded_average());
old_gen->setNearLargestChunk();
{
SweepClosure sweepClosure(this, old_gen, &_markBitMap, CMSYield);
old_gen->cmsSpace()->blk_iterate_careful(&sweepClosure);
// We need to free-up/coalesce garbage/blocks from a
// co-terminal free run. This is done in the SweepClosure
// destructor; so, do not remove this scope, else the
// end-of-sweep-census below will be off by a little bit.
}
old_gen->cmsSpace()->sweep_completed();
old_gen->cmsSpace()->endSweepFLCensus(sweep_count());
if (should_unload_classes()) { // unloaded classes this cycle,
_concurrent_cycles_since_last_unload = 0; // ... reset count
} else { // did not unload classes,
_concurrent_cycles_since_last_unload++; // ... increment count
}
}
CMS只會回收CMSGen,也就是老年代,這里需要重新說明一下;除了ConcMarkSweepGC外,其他GC類型的OldGC都可以說是FullGC(G1暫未了解),具體的sweep算法就不繼續分析了。
foreground gc
上面說到的屬于CMS周期性GC,也就是background gc,是一種被動的GC,通過監控老年代空間使用率來啟動GC,foreground gc屬于主動gc,發生foreground gc一般來說就是年輕代發生了Minor GC,并且發生了"Promotion fail",老年代空間不足等原因,具體原因和GenCollectedHeap堆的GC策略相關,這一點可以看前面的分析文章;下面來簡單分析一下foreground gc的一些情況;
發生foreground gc的入口是ConcurrentMarkSweepGeneration::collect;
void ConcurrentMarkSweepGeneration::collect(bool full,
bool clear_all_soft_refs,
size_t size,
bool tlab)
{
collector()->collect(full, clear_all_soft_refs, size, tlab);
}
void CMSCollector::collect(bool full,
bool clear_all_soft_refs,
size_t size,
bool tlab)
{
// The following "if" branch is present for defensive reasons.
// In the current uses of this interface, it can be replaced with:
// assert(!GCLocker.is_active(), "Can't be called otherwise");
// But I am not placing that assert here to allow future
// generality in invoking this interface.
if (GCLocker::is_active()) {
// A consistency test for GCLocker
assert(GCLocker::needs_gc(), "Should have been set already");
// Skip this foreground collection, instead
// expanding the heap if necessary.
// Need the free list locks for the call to free() in compute_new_size()
compute_new_size();
return;
}
acquire_control_and_collect(full, clear_all_soft_refs);
}
acquire_control_and_collect函數將完成foreground gc的工作,看函數名字就可以猜測它要干嘛,首先要acquire control,也就是獲取到堆的控制權,因為在觸發foreground gc的時候,background gc可能正在工作,因為不可能同時兩中gc同時運行,而foreground gc的優先級明顯高于background gc,所以需要讓background gc放棄gc,然后foreground gc來完成收集老年代垃圾的工作,當然,foreground gc順帶會回收新生代,所以是一次FullGC,下面具體看看acquire_control_and_collect函數的流程;
{
MutexLockerEx x(CGC_lock, Mutex::_no_safepoint_check_flag);
if (_foregroundGCShouldWait) {
// We are going to be waiting for action for the CMS thread;
// it had better not be gone (for instance at shutdown)!
assert(ConcurrentMarkSweepThread::cmst() != NULL && !ConcurrentMarkSweepThread::cmst()->has_terminated(),
"CMS thread must be running");
// Wait here until the background collector gives us the go-ahead
ConcurrentMarkSweepThread::clear_CMS_flag(
ConcurrentMarkSweepThread::CMS_vm_has_token); // release token
// Get a possibly blocked CMS thread going:
// Note that we set _foregroundGCIsActive true above,
// without protection of the CGC_lock.
CGC_lock->notify();
assert(!ConcurrentMarkSweepThread::vm_thread_wants_cms_token(),
"Possible deadlock");
while (_foregroundGCShouldWait) {
// wait for notification
CGC_lock->wait(Mutex::_no_safepoint_check_flag);
// Possibility of delay/starvation here, since CMS token does
// not know to give priority to VM thread? Actually, i think
// there wouldn't be any delay/starvation, but the proof of
// that "fact" (?) appears non-trivial. XXX 20011219YSR
}
ConcurrentMarkSweepThread::set_CMS_flag(
ConcurrentMarkSweepThread::CMS_vm_has_token);
}
}
這一段會嘗試等background gc主動把堆的控制權轉移給foreground gc,在collect_in_background(background gc)中,開始之前會判斷是否在進行foreground gc(_foregroundGCIsActive = true),如果在執行foreground gc,那么就會直接退出本次background gc;否則再每完成一個階段之后都會嘗試判斷是否foreground gc在等待;
{
// Check if the FG collector wants us to yield.
CMSTokenSync x(true); // is cms thread
if (waitForForegroundGC()) {
// We yielded to a foreground GC, nothing more to be
// done this round.
assert(_foregroundGCShouldWait == false, "We set it to false in "
"waitForForegroundGC()");
log_debug(gc, state)("CMS Thread " INTPTR_FORMAT " exiting collection CMS state %d",
p2i(Thread::current()), _collectorState);
return;
} else {
// The background collector can run but check to see if the
// foreground collector has done a collection while the
// background collector was waiting to get the CGC_lock
// above. If yes, break so that _foregroundGCShouldWait
// is cleared before returning.
if (_collectorState == Idling) {
break;
}
}
}
waitForForegroundGC函數完成等待foreground gc 發生的工作:
bool CMSCollector::waitForForegroundGC() {
bool res = false;
assert(ConcurrentMarkSweepThread::cms_thread_has_cms_token(),
"CMS thread should have CMS token");
// Block the foreground collector until the
// background collectors decides whether to
// yield.
MutexLockerEx x(CGC_lock, Mutex::_no_safepoint_check_flag);
_foregroundGCShouldWait = true;
if (_foregroundGCIsActive) {
// The background collector yields to the
// foreground collector and returns a value
// indicating that it has yielded. The foreground
// collector can proceed.
res = true;
_foregroundGCShouldWait = false;
ConcurrentMarkSweepThread::clear_CMS_flag(
ConcurrentMarkSweepThread::CMS_cms_has_token);
ConcurrentMarkSweepThread::set_CMS_flag(
ConcurrentMarkSweepThread::CMS_cms_wants_token);
// Get a possibly blocked foreground thread going
CGC_lock->notify();
log_debug(gc, state)("CMS Thread " INTPTR_FORMAT " waiting at CMS state %d",
p2i(Thread::current()), _collectorState);
while (_foregroundGCIsActive) {
CGC_lock->wait(Mutex::_no_safepoint_check_flag);
}
ConcurrentMarkSweepThread::set_CMS_flag(
ConcurrentMarkSweepThread::CMS_cms_has_token);
ConcurrentMarkSweepThread::clear_CMS_flag(
ConcurrentMarkSweepThread::CMS_cms_wants_token);
}
log_debug(gc, state)("CMS Thread " INTPTR_FORMAT " continuing at CMS state %d",
p2i(Thread::current()), _collectorState);
return res;
}
如果此時進行(或者等待)foreground gc,那么就放棄此次background gc;否則告訴后續來到的foreground gc等待一下,等本階段CMS GC完成會再次來判斷的;
在foreground gc中,獲取到了堆的控制權之后,就會執行下面的代碼片段:
if (first_state > Idling) {
report_concurrent_mode_interruption();
}
void CMSCollector::report_concurrent_mode_interruption() {
if (is_external_interruption()) {
log_debug(gc)("Concurrent mode interrupted");
} else {
log_debug(gc)("Concurrent mode failure");
_gc_tracer_cm->report_concurrent_mode_failure();
}
}
bool CMSCollector::is_external_interruption() {
GCCause::Cause cause = GenCollectedHeap::heap()->gc_cause();
return GCCause::is_user_requested_gc(cause) ||
GCCause::is_serviceability_requested_gc(cause);
}
我們在觀察CMS GC日志的時候,偶爾會看到“Concurrent mode interrupted”或者“Concurrent mode failure”這樣的日志,就是因為在進行foreground gc的時候發現background gc已經在工作了;如果是類似于System.gc()這樣的用戶請求GC,那么就會打印“Concurrent mode interrupted”,否則就是“Concurrent mode failure”;
之后CMSCollector::do_compaction_work函數將做一次Mark-sweep-compact的工作,具體的工作在GenMarkSweep::invoke_at_safepoint函數中完成,這個函數在前面分析Serial Old的時候提到過,所以不再贅述;
總結
整個CMS GC其實是非常復雜的,涉及用戶線程和GC線程并發執行,以及foreground gc和background gc相互配合的過程,當然還涉及大量的參數,這些參數稍微不注意就會讓JVM工作得不好,所以建議在不了解某個參數的具體表現的時候不要輕易使用;
其實CMS Old GC為什么分這么多步驟呢?主要原因是為了降低STW的時候,所以將mark和sweep兩個階段都設計成并發了,initMark和FinalMark會STW,但是initMark階段所做的mark非常有限,GCRoot-> cms gen , YoungGen -> cms gen,而且因為兩個preclan階段和Dirty Card的存在,使得FinalMark階段需要掃描的對象大大減小,如果在實際的運行過程中發現每次FinalMark過程都非常長,那么就設置參數在進行FinalMark之前進行一次YGC,使得FinalMark需要掃描的對象減少;CMS Old GC Mark 和 preclean階段允許用戶線程和GC線程并發執行,所以會存在:
- (1)、yong gen -> old gen
- (2)、GCRoot -> old gen
- (3)、old gen internal ref changed
解決這些問題就需要FinalMark的存在,FinalMark將掃描新生代,標記出yong gen -> old gen的部分,老年代內部的對象引用關系如果在并發階段發生變化,會記錄到DirtyCard中去,所以在FinalMark階段掃描DirtyCard即可;
最后要說一下foreground gc和background gc,最好不要發生foreground gc,因為foreground gc會認為此時已經沒有什么辦法滿足對象分配了,那么就要做一次徹底清理的工作,也就是FullGC,并且foreground gc是單線程運行的,并且是mark-sweep-compact的,所以速度可想而知,如果發現foreground gc發生的頻繁,就要分析一下原因了,建議去研究GenCollectedHeap::do_collection,搞明白GC的策略,當然不同GC對應的堆是不一樣的,Serial 和 CMS對應的是GenCollectedHeap,其他的就不是了,這個前面的文章說過。