Up Top

Memory allocation (& GC 処理) : メモリの確保処理 (GC 処理) : slow-path の処理 (4) GC 処理 : ParallelScavengeHeap の場合 : GC 処理の並列化の方針について


概要(Summary)

ParallelScavenge では, 2段階の並列化が行われている.

  1. GCTask 単位での並列化 (大きな粒度での並列化)
  2. TaskQueue 内のポインタ単位での並列化 (小さな粒度での並列化)

より具体的に言うと, 以下のような並列化が行われる.

  1. まず, 複数の GCTask オブジェクトが GCTaskQueue というキューに詰められた状態で GC 処理が開始される.

各 GCTaskThread は GCTaskQueue から早い者勝ちで GCTask を取り出し, そこに書かれている処理を行っていく. 取り出した GCTask の処理が終わったら, また新しい GCTask を取り出して処理を続ける.

  1. 処理が進み GCTaskQueue から GCTask がどんどん取り出されていくと, 最後には StealTask や StealMarkingTask が取り出される.

(StealTask/StealMarkingTask は GCTaskThread の個数分だけ GCTaskQueue に詰められている. このため最終的には全員が StealTask/StealMarkingTask を実行することになる)

StealTask/StealMarkingTask は他の GCTaskThread の処理を奪ってくる GCTask. これによりポインタ単位での(小さな粒度での)負荷分散が行われる.

備考(Notes)

(以下は, "1. GCTask 単位での並列化" に関する補足説明)

GCTask には幾つかの種類があるが, 典型的な処理パターンは以下のようになる (詳細は PSScavenge::invoke_no_policy() や PSParallelCompact::invoke_no_policy() を参照. (See: here, here and here for details)).

Minor GC 時の処理パターン

  1. その GCTask が対象とする範囲を探索し, 見つかったポインタを全て PSPromotionManager オブジェクトに集める.

(より正確には, PSPromotionManager 内の claimed_stack_depth() という OopStarTaskQueue に集めてくる. OopStarTaskQueue なので, 中には oop* または narrowOop* が入っている)

(この処理には, oops_do() を使う場合には, PSScavengeRootsClosure クロージャーが使われる. oops_do() を使わない場合は, oopDesc::push_contents() を使って回収が行われる.)

  1. PSPromotionManager::drain_stacks() で, 集めたポインタを再帰的に辿っていって GC 処理を行う,

PSPromotionManager (と StealTask) による並列化の詳細

PSPromotionManager オブジェクト内には, 各 GCTaskThread が仕事を記憶しておくためのスタックとして _claimed_stack_depth という OverflowTaskQueue (ソースコード上では typedef による別名の OopStarTaskQueue) が用意されている.

    ((cite: hotspot/src/share/vm/gc_implementation/parallelScavenge/psPromotionManager.hpp))
      OopStarTaskQueue                    _claimed_stack_depth;

そして, このスタックの中身を複数の GCTaskThread 間でバランシングする仕組みとして, PSPromotionManager クラスの static フィールドに _stack_array_depth という GenericTaskQueueSet (ソースコード上での型は OopStarTaskQueueSet) が用意されている (static 変数なので全体で一個).

    ((cite: hotspot/src/share/vm/gc_implementation/parallelScavenge/psPromotionManager.hpp))
      static OopStarTaskQueueSet*         _stack_array_depth;

(なお, アクセサは PSPromotionManager::stack_array_depth())

    ((cite: hotspot/src/share/vm/gc_implementation/parallelScavenge/psPromotionManager.hpp))
      static OopStarTaskQueueSet* stack_array_depth()   { return _stack_array_depth; }

この GenericTaskQueueSet には, 全ての PSPromotionManager オブジェクトの claimed_stack_depth が登録されている. (つまりここから steal すれば他のスレッドの仕事を奪える. 実際に, StealTask はここから仕事を盗ってくる. See: PSPromotionManager::stealdepth())

なお, stack_array_depth フィールドのオブジェクトは HotSpot の起動時に実行される PSPromotionManager::initialize() 内で生成されている. (この際に, 各 GCTaskThread 用の PSPromotionManager オブジェクトも生成されている. また, 各 PSPromotionManager オブジェクト内の claimedstack_depth を _stack_array_depth に登録する作業も行われている)

    ((cite: hotspot/src/share/vm/gc_implementation/parallelScavenge/psPromotionManager.cpp))
    void PSPromotionManager::initialize() {
    ...
      _stack_array_depth = new OopStarTaskQueueSet(ParallelGCThreads);
      guarantee(_stack_array_depth != NULL, "Cound not initialize promotion manager");

      // Create and register the PSPromotionManager(s) for the worker threads.
      for(uint i=0; i<ParallelGCThreads; i++) {
        _manager_array[i] = new PSPromotionManager();
        guarantee(_manager_array[i] != NULL, "Could not create PSPromotionManager");
        stack_array_depth()->register_queue(i, _manager_array[i]->claimed_stack_depth());
      }

Major GC 時の処理パターン

  1. その GCTask が対象とする範囲を探索し, 見つかったポインタを全て ParCompactionManager オブジェクトに集める.

(より正確には, ParCompactionManager 内の marking_stack() という OverflowTaskQueue に集めてくる.)

(この処理には, PSParallelCompact::MarkAndPushClosure クロージャーが使われる)

  1. ParCompactionManager::follow_marking_stacks() で, 集めたポインタを再帰的に辿っていって GC 処理を行う,

ParCompactionManager (と StealMarkingTask/StealRegionCompactionTask) による並列化の詳細

ParCompactionManager オブジェクト内には, 各 GCTaskThread が仕事を記憶しておくためのスタックとして 以下の 3本の OverflowTaskQueue (ソースコード上での型はそれぞれ OverflowTaskQueue, ObjArrayTaskQueue, RegionTaskQueue) が用意されている.

    ((cite: hotspot/src/share/vm/gc_implementation/parallelScavenge/psCompactionManager.hpp))
      OverflowTaskQueue<oop>        _marking_stack;
      ObjArrayTaskQueue             _objarray_stack;

      // Is there a way to reuse the _marking_stack for the
      // saving empty regions?  For now just create a different
      // type of TaskQueue.
      RegionTaskQueue               _region_stack;

そして, これらのスタックの中身を複数の GCTaskThread 間でバランシングする仕組みとして, ParCompactionManager クラスの static フィールドに 以下の3本の GenericTaskQueueSet (ソースコード上での型は OopTaskQueueSet, ObjArrayTaskQueueSet, RegionTaskQueueSet) が用意されている (static 変数なので全体で一個).

    ((cite: hotspot/src/share/vm/gc_implementation/parallelScavenge/psCompactionManager.hpp))
      static OopTaskQueueSet*       _stack_array;
      static ObjArrayTaskQueueSet*  _objarray_queues;
    ...
      static RegionTaskQueueSet*    _region_array;

これらの GenericTaskQueueSet には, 全ての ParCompactionManager オブジェクトの marking_stack, _objarray_stack, _region_stack が, それぞれ登録されている. (つまりここから steal すれば他のスレッドの仕事を奪える. 実際に, StealMarkingTask や StealRegionCompactionTask はここから仕事を盗ってくる. See: ParCompactionManager::stealobjarray(), ParCompactionManager::steal())

なお, これらの GenericTaskQueueSet は HotSpot の起動時に実行される ParCompactionManager::initialize() 内で生成されている. (この際に, 各 GCTaskThread 用の ParCompactionManager オブジェクトも生成されている. また, 各 ParCompactionManager オブジェクト内の OverflowTaskQueue を 対応する GenericTaskQueueSet に登録する作業も行われている)

    ((cite: hotspot/src/share/vm/gc_implementation/parallelScavenge/psCompactionManager.cpp))
    void ParCompactionManager::initialize(ParMarkBitMap* mbm) {
    ...
      _stack_array = new OopTaskQueueSet(parallel_gc_threads);
      guarantee(_stack_array != NULL, "Could not allocate stack_array");
      _objarray_queues = new ObjArrayTaskQueueSet(parallel_gc_threads);
      guarantee(_objarray_queues != NULL, "Could not allocate objarray_queues");
      _region_array = new RegionTaskQueueSet(parallel_gc_threads);
      guarantee(_region_array != NULL, "Could not allocate region_array");

      // Create and register the ParCompactionManager(s) for the worker threads.
      for(uint i=0; i<parallel_gc_threads; i++) {
        _manager_array[i] = new ParCompactionManager();
        guarantee(_manager_array[i] != NULL, "Could not create ParCompactionManager");
        stack_array()->register_queue(i, _manager_array[i]->marking_stack());
        _objarray_queues->register_queue(i, &_manager_array[i]->_objarray_stack);
        region_array()->register_queue(i, _manager_array[i]->region_stack());
      }

備考(Notes)

(以下は, "1. GCTask 単位での並列化" に関する補足説明)

ポインタ配列については, あまり大きいものは一度に処理せず少しずつ処理している模様


This document is available under the GNU GENERAL PUBLIC LICENSE Version 2.