ParallelScavenge では, 2段階の並列化が行われている.
より具体的に言うと, 以下のような並列化が行われる.
各 GCTaskThread は GCTaskQueue から早い者勝ちで GCTask を取り出し, そこに書かれている処理を行っていく. 取り出した GCTask の処理が終わったら, また新しい GCTask を取り出して処理を続ける.
(StealTask/StealMarkingTask は GCTaskThread の個数分だけ GCTaskQueue に詰められている. このため最終的には全員が StealTask/StealMarkingTask を実行することになる)
StealTask/StealMarkingTask は他の GCTaskThread の処理を奪ってくる GCTask. これによりポインタ単位での(小さな粒度での)負荷分散が行われる.
(以下は, "1. GCTask 単位での並列化" に関する補足説明)
GCTask には幾つかの種類があるが, 典型的な処理パターンは以下のようになる (詳細は PSScavenge::invoke_no_policy() や PSParallelCompact::invoke_no_policy() を参照. (See: here, here and here for details)).
(より正確には, PSPromotionManager 内の claimed_stack_depth() という OopStarTaskQueue に集めてくる. OopStarTaskQueue なので, 中には oop* または narrowOop* が入っている)
(この処理には, oops_do() を使う場合には, PSScavengeRootsClosure クロージャーが使われる. oops_do() を使わない場合は, oopDesc::push_contents() を使って回収が行われる.)
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());
}
(より正確には, ParCompactionManager 内の marking_stack() という OverflowTaskQueue
(この処理には, PSParallelCompact::MarkAndPushClosure クロージャーが使われる)
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());
}
(以下は, "1. GCTask 単位での並列化" に関する補足説明)
ポインタ配列については, あまり大きいものは一度に処理せず少しずつ処理している模様
Minor GC 時の処理: (See: is_oop_masked(), mask_chunked_array_oop())
Major GC 時の処理: (See: objArrayKlass::oop_follow_contents()).
This document is available under the GNU GENERAL PUBLIC LICENSE Version 2.