当前位置: 首页 > 工具软件 > jfinal cms > 使用案例 >

HotSpot CMS收集器

席波娃
2023-12-01

HotSpot中CMS收集器通过牺牲系统吞吐量来实现响应速度优先。适合追求垃圾收集速度的服务器上。

  1. CMS收集器属于分代收集器
  2. 年轻代算法并发复制算法,老年代算法为并发标记清除算法。
  3. 年轻代分代为ParNewGeneration,老年代分代为ConcurrentMarkSweepGeneration。
  4. 适用于对响应时间要求高的应用场景。

一.CMS收集器初始化

使用-XX:+UseConcMarkSweepGC启用CMS。
hotspot/src/share/vm/runtime/init.cpp

jint init_globals() { jint status = universe_init();}

hotspot/src/share/vm/memory/universe.cpp

jint universe_init() { jint status = Universe::initialize_heap();}

hotspot/src/share/vm/memory/universe.cpp

jint Universe::initialize_heap() {
  _collectedHeap = create_heap();
  status = _collectedHeap->initialize();
}

hotspot/src/share/vm/memory/universe.cpp
使用UseConcMarkSweepGC策略创建堆,和Serial收集器一样使用分代堆GenCollectedHeap,收集策略为ConcurrentMarkSweepPolicy继承自分代策略GenCollectorPolicy

CollectedHeap* Universe::create_heap() {
 if (UseParallelGC) {
    return Universe::create_heap_with_policy<ParallelScavengeHeap, GenerationSizer>();
  } else if (UseG1GC) {
    return Universe::create_heap_with_policy<G1CollectedHeap, G1CollectorPolicy>();
  } else if (UseConcMarkSweepGC) {
    return Universe::create_heap_with_policy<GenCollectedHeap, ConcurrentMarkSweepPolicy>();
  } else if (UseSerialGC) {
    return Universe::create_heap_with_policy<GenCollectedHeap, MarkSweepPolicy>();
  }
}

hotspot/src/share/vm/memory/universe.inline.hpp
策略模板函数

template <class Heap, class Policy>
CollectedHeap* Universe::create_heap_with_policy() {
  Policy* policy = new Policy(); //创建收集策略 MarkSweepPolicy是GenCollectorPolicy的一种
  policy->initialize_all();
  return new Heap(policy); //创建分代堆具体来说就是GenCollectedHeap
}

hotspot/src/share/vm/gc/shared/genCollectedHeap.cpp
分代堆创建WorkGang

GenCollectedHeap::GenCollectedHeap(GenCollectorPolicy *policy) :
  CollectedHeap(),...
{
  //创建WorkGang来管理GC线程
  if (UseConcMarkSweepGC) {
    _workers = new WorkGang("GC Thread", ParallelGCThreads,
                            /* are_GC_task_threads */true,
                            /* are_ConcurrentGC_threads */false);
    _workers->initialize_workers();
  } else {
    // Serial GC does not use workers.
    _workers = NULL;
  }
}

hotspot/src/share/vm/gc/shared/genCollectedHeap.cpp
创建CMSCollector

jint GenCollectedHeap::initialize() {
 ......
#if INCLUDE_ALL_GCS
  // If we are running CMS, create the collector responsible
  // for collecting the CMS generations.
  if (collector_policy()->is_concurrent_mark_sweep_policy()) {
    bool success = create_cms_collector();
  }
#endif // INCLUDE_ALL_GCS

  return JNI_OK;
}

hotspot/src/share/vm/gc/cms/concurrentMarkSweepGeneration.cpp
初始化CMSCollector,创建YieldingFlexibleWorkGang管理CMS GC线程,创建后台常驻线程ConcurrentMarkSweepThread用于CMS后台垃圾收集

CMSCollector::CMSCollector(ConcurrentMarkSweepGeneration* cmsGen,...): ...
{
  _cmsGen->cmsSpace()->set_collector(this);
  _markBitMap.allocate(_span)//位图
  _modUnionTable.allocate(_span);
  _markStack.allocate(MarkStackSize)//标记栈
  //多线程收集创建YieldingFlexibleWorkGang,管理CMS GC Thread
  //为每个线程创建一个任务队列
   if (ConcGCThreads > 1) {
      _conc_workers = new YieldingFlexibleWorkGang("CMS Thread",ConcGCThreads, true);
      _conc_workers->initialize_workers();
   	  _task_queues = new OopTaskQueueSet(num_queues);
      for (i = 0; i < num_queues; i++) {
        PaddedOopTaskQueue *q = new PaddedOopTaskQueue();
        _task_queues->register_queue(i, q);
      }
      for (i = 0; i < num_queues; i++) {
        _task_queues->queue(i)->initialize();
        _hash_seed[i] = 17;  // copied from ParNew
      }
    }
  }
  // 后台长期运行的CMS线程,用于CMS后台垃圾收集
  _cmsThread = ConcurrentMarkSweepThread::start(this);

}

hotspot/src/share/vm/gc/shared/generationSpec.cpp
创建对应的收集器分代类型,CMS收集器对应ParNewGeneration和ConcurrentMarkSweepGeneration分代


#if INCLUDE_ALL_GCS
#include "gc/cms/concurrentMarkSweepGeneration.hpp"
#include "gc/cms/parNewGeneration.hpp"
#endif // INCLUDE_ALL_GCS

Generation* GenerationSpec::init(ReservedSpace rs, CardTableRS* remset) {
  switch (name()) {
    case Generation::DefNew:
      return new DefNewGeneration(rs, init_size());
    case Generation::MarkSweepCompact:
      return new TenuredGeneration(rs, init_size(), remset);
    #if INCLUDE_ALL_GCS
    case Generation::ParNew:
      return new ParNewGeneration(rs, init_size());
    case Generation::ConcurrentMarkSweep: {
      ConcurrentMarkSweepGeneration* g = NULL;
      g = new ConcurrentMarkSweepGeneration(rs, init_size(), remset);
      g->initialize_performance_counters();
      return g;
    }
    #endif // INCLUDE_ALL_GCS
}

hotspot/src/share/vm/gc/cms/parNewGeneration.cpp
继承自串行收集器的年轻代DefNewGeneration

ParNewGeneration::ParNewGeneration(ReservedSpace rs, size_t initial_byte_size)
  : DefNewGeneration(rs, initial_byte_size, "PCopy"),
{
  //创建任务队列集合
  _task_queues = new ObjToScanQueueSet(ParallelGCThreads);
  //每一个收集线程对应一个队列
  for (uint i = 0; i < ParallelGCThreads; i++) {
    ObjToScanQueue *q = new ObjToScanQueue();
    guarantee(q != NULL, "work_queue Allocation failure.");
    _task_queues->register_queue(i, q);
  }
  //初始化任务队列
  for (uint i = 0; i < ParallelGCThreads; i++) {
    _task_queues->queue(i)->initialize();
  }
}

hotspot/src/share/vm/gc/cms/concurrentMarkSweepGeneration.cpp
同串行收集器一样继承自CardGeneration

ConcurrentMarkSweepGeneration::ConcurrentMarkSweepGeneration(
     ReservedSpace rs, size_t initial_byte_size, CardTableRS* ct) :
  CardGeneration(rs, initial_byte_size, ct),
  _dilatation_factor(((double)MinChunkSize)/((double)(CollectedHeap::min_fill_size()))),
  _did_compact(false)
{
  HeapWord* bottom = (HeapWord*) _virtual_space.low();
  HeapWord* end    = (HeapWord*) _virtual_space.high();
  //映射一块空间
  _cmsSpace = new CompactibleFreeListSpace(_bts, MemRegion(bottom, end));
  _cmsSpace->_old_gen = this;
  _gc_stats = new CMSGCStats();
  //为每一个线程创建一个CMSParGCThreadState
  _par_gc_thread_states = NEW_C_HEAP_ARRAY(CMSParGCThreadState*, ParallelGCThreads, mtGC);
  for (uint i = 0; i < ParallelGCThreads; i++) {
    _par_gc_thread_states[i] = new CMSParGCThreadState(cmsSpace());
  }

}

二.垃圾收集

CMS的年轻代,老年代的分代,堆空间,收集策略和Serial收集器类似,不再赘述。参考Serial收集器一篇。

1.年轻代GC

hotspot/src/share/vm/gc/cms/parNewGeneration.cpp
CMS的年轻代GC总是由收集策略决定,从模板策略函数collect进入。同Serial收集器一样,使用复制算法,将Eden空间的对象拷贝提升到to空间。各Closure分别对应各种对象遍历操作。主要流程:

  1. 遍历堆空间,线程栈等root引用链,将对象提升。
  2. 遍历所有promote到老年代的对象,恢复其对象头。
  3. Survivor的form和to空间交换。
void ParNewGeneration::collect(bool   full,
                               bool   clear_all_soft_refs,
                               size_t size,
                               bool   is_tlab) {
  //年轻代堆
  GenCollectedHeap* gch = GenCollectedHeap::heap();
  //自适应收集策略
  AdaptiveSizePolicy* size_policy = gch->gen_policy()->size_policy();
  //多GC线程管理
  WorkGang* workers = gch->workers();
  //计算活跃的GC工作线程
  uint active_workers =
       AdaptiveSizePolicy::calc_active_workers(workers->total_workers(),
                                               workers->active_workers(),
                                               Threads::number_of_non_daemon_threads());
  active_workers = workers->update_active_workers(active_workers);
  //获取老年代
  _old_gen = gch->old_gen();
  //是否使用自适应策略
  if (UseAdaptiveSizePolicy) {
    set_survivor_overflow(false);
    size_policy->minor_collection_begin();
  }
  age_table()->clear();
  to()->clear(SpaceDecorator::Mangle);
  gch->save_marks();
  // Set the correct parallelism (number of queues) in the reference processor
  ref_processor()->set_active_mt_degree(active_workers);
  _preserved_marks_set.init(active_workers);
  //并发任务终结者
  ParallelTaskTerminator _term(active_workers, task_queues());
  //线程任务状态集合
  ParScanThreadStateSet thread_state_set(active_workers,
                                         *to(), *this, *_old_gen, *task_queues(),
                                         _overflow_stacks, _preserved_marks_set,
                                         desired_plab_sz(), _term);
  thread_state_set.reset(active_workers, promotion_failed());

  {
    StrongRootsScope srs(active_workers);
    //创建任务
    ParNewGenTask tsk(this, _old_gen, reserved().end(), &thread_state_set, &srs);
    gch->rem_set()->prepare_for_younger_refs_iterate(true);
    //派发任务
    if (workers->total_workers() > 1) {
      workers->run_task(&tsk);
    } else {
      tsk.work(0);
    }
  }
  ReferenceProcessor* rp = ref_processor();
  IsAliveClosure is_alive(this);
  ScanWeakRefClosure scan_weak_ref(this);
  KeepAliveClosure keep_alive(&scan_weak_ref);
  ScanClosure               scan_without_gc_barrier(this, false);
  ScanClosureWithParBarrier scan_with_gc_barrier(this, true);
  set_promo_failure_scan_stack_closure(&scan_without_gc_barrier);
  EvacuateFollowersClosureGeneral evacuate_followers(gch,
    &scan_without_gc_barrier, &scan_with_gc_barrier);
  rp->setup_policy(clear_all_soft_refs);
  // Can  the mt_degree be set later (at run_task() time would be best)?
  rp->set_active_mt_degree(active_workers);
  ReferenceProcessorStats stats;
  if (rp->processing_is_mt()) { //多线程
    ParNewRefProcTaskExecutor task_executor(*this, *_old_gen, thread_state_set);
    stats = rp->process_discovered_references(&is_alive, &keep_alive,
                                              &evacuate_followers, &task_executor,
                                              _gc_timer);
  } else {
    thread_state_set.flush();
    gch->save_marks();
    stats = rp->process_discovered_references(&is_alive, &keep_alive,
                                              &evacuate_followers, NULL,
                                              _gc_timer);
  }
  if (!promotion_failed()) { //提升失败
    // Swap the survivor spaces.
    eden()->clear(SpaceDecorator::Mangle);
    from()->clear(SpaceDecorator::Mangle);
    swap_spaces();
    size_policy->reset_gc_overhead_limit_count();
    adjust_desired_tenuring_threshold();
  } else {
    handle_promotion_failed(gch, thread_state_set);
  }
}

2.老年代前台GC

老年代GC主要使用CMS算法。分为前后台收集方式。前台收集由用户触发。后台GC由常驻线程ConcurrentMarkSweepThread 定时等待触发。

jdk/src/java.base/share/native/libjava/Runtime.c
上一篇在触发GC的代码中我们循环创建大对象来触发GC,使用System.gc()来触发CMS的FullGC。

JNIEXPORT void JNICALL
Java_java_lang_Runtime_gc(JNIEnv *env, jobject this)
{
    JVM_GC();
}

hotspot/src/share/vm/prims/jvm.cpp
可以使用DisableExplicitGC来禁止触发FullGC

JVM_ENTRY_NO_ENV(void, JVM_GC(void))
  JVMWrapper("JVM_GC");
  if (!DisableExplicitGC) {
    Universe::heap()->collect(GCCause::_java_lang_system_gc);
  }
JVM_END

hotspot/src/share/vm/gc/shared/genCollectedHeap.cpp

void GenCollectedHeap::collect(GCCause::Cause cause) {
 // Stop-the-world full collection.
    collect(cause, OldGen);
    ......
}

hotspot/src/share/vm/gc/shared/genCollectedHeap.cpp

void GenCollectedHeap::collect(GCCause::Cause cause, GenerationType max_generation) {
  collect_locked(cause, max_generation);
}

hotspot/src/share/vm/gc/shared/genCollectedHeap.cpp

void GenCollectedHeap::collect_locked(GCCause::Cause cause, GenerationType max_generation) {
  {
    MutexUnlocker mu(Heap_lock);  // give up heap lock, execute gets it back
    VM_GenCollectFull op(gc_count_before, full_gc_count_before,
                         cause, max_generation);
    VMThread::execute(&op); //触发FullGc
  }
}

hotspot/src/share/vm/gc/cms/concurrentMarkSweepGeneration.cpp
GC触发后将由收集策略转发到这里执行

void ConcurrentMarkSweepGeneration::collect(bool   full,...)
{
  collector()->collect(full, clear_all_soft_refs, size, tlab);
}

hotspot/src/share/vm/gc/cms/concurrentMarkSweepGeneration.cpp

void CMSCollector::collect(bool   full,...)
{
  acquire_control_and_collect(full, clear_all_soft_refs);
}

hotspot/src/share/vm/gc/cms/concurrentMarkSweepGeneration.cpp
使用同串行收集器老年代一样的收集算法

  1. 使用标记栈标记存活对象
  2. 为标记栈中的对象计算一个新地址
  3. 将新地址更新到对象指针
  4. 移动对象到新地址处
void CMSCollector::do_compaction_work(bool clear_all_soft_refs) {
  GenCollectedHeap* gch = GenCollectedHeap::heap();
  ....
  GenMarkSweep::invoke_at_safepoint(ref_processor(), clear_all_soft_refs);
  ....
}

3.老年代后台GC

后台常驻线程ConcurrentMarkSweepThread启动后会一直循环检测GC条件,触发后台GC

void ConcurrentMarkSweepThread::run_service() {

  while (!should_terminate()) {
    sleepBeforeNextCycle();
    if (should_terminate()) break;
    GCIdMark gc_id_mark;
    GCCause::Cause cause = _collector->_full_gc_requested ?
      _collector->_full_gc_cause : GCCause::_cms_concurrent_mark;
    _collector->collect_in_background(cause);
  }
}

hotspot/src/share/vm/gc/cms/concurrentMarkSweepGeneration.cpp
后台GC采用并发标记清除算法,流程如下:

  1. 初始标记(CMS-initial-mark) ,会导致stw,标记老年代中所有的GC Roots对象,标记年轻代中活着的对象引用到的老年代的对象
  2. 并发标记(CMS-concurrent-mark),与用户线程同时运行,并发标记阶段只负责将引用发生改变的Card标记为Dirty状态,不负责处理
  3. 预清理(CMS-concurrent-preclean),与用户线程同时运行,这个阶段就是用来处理前一个阶段因为引用关系改变导致没有标记到的存活对象的,它会扫描所有标记为Dirty的Card
  4. 可被终止的预清理(CMS-concurrent-abortable-preclean) 与用户线程同时运行,试着去承担下一个阶段Final Remark阶段足够多的工作
  5. 重新标记(CMS-remark) ,会导致swt,该阶段的任务是完成标记整个年老代的所有的存活对象。
  6. 并发清除(CMS-concurrent-sweep),与用户线程同时运行,这个阶段主要是清除那些没有标记的对象并且回收空间,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们
  7. 并发重置状态等待下次CMS的触发(CMS-concurrent-reset),与用户线程同时运行;
void CMSCollector::collect_in_background(GCCause::Cause cause) {
  GenCollectedHeap* gch = GenCollectedHeap::heap();
  size_t prev_used = _cmsGen->used();
    switch (_collectorState) { //初始标记触发VM_CMS_Initial_Mark
      case InitialMarking:
        {
          ReleaseForegroundGC x(this); //加锁
          stats().record_cms_begin();
          VM_CMS_Initial_Mark initial_mark_op(this);
          VMThread::execute(&initial_mark_op);
        }
        break;
      case Marking: // 并发标记
        // initial marking in checkpointRootsInitialWork has been completed
        markFromRoots()
        break;
      case Precleaning: //预清理
        // marking from roots in markFromRoots has been completed
        preclean();
        break;
      case AbortablePreclean: //可被终止的预清理
        abortable_preclean();
        break;
      case FinalMarking: //重新标记
        {
          ReleaseForegroundGC x(this);
          VM_CMS_Final_Remark final_remark_op(this);
          VMThread::execute(&final_remark_op);
        }
        break;
      case Sweeping:  //并发清除
        // final marking in checkpointRootsFinal has been completed
        sweep();
      case Resizing: {
        {
          ReleaseForegroundGC x(this);   // 解锁
          MutexLockerEx       y(Heap_lock, Mutex::_no_safepoint_check_flag);
          CMSTokenSync        z(true);   // not strictly needed.
          if (_collectorState == Resizing) {
            compute_new_size();
            save_heap_summary();
            _collectorState = Resetting;
          } 
        }
        break;
      }
      case Resetting:  //并发重置状态等待下次CMS的触发
        // CMS heap resizing has been completed
        reset_concurrent();
        MetaspaceGC::set_should_concurrent_collect(false);
        stats().record_cms_end();
        break;
      case Idling:
      default:
        ShouldNotReachHere();
        break;
    }
  }
}

CMS收集器将标记清除切分为数个阶段,其中一些阶段可与应用程序一起运行,这会占用一部分系统资源,导致系统吞吐量降低,需要STW的回收阶段用时变小了,这部分耗时相对减少了,所以系统的响应速度提升了。

 类似资料: