32
1 並並 GC 並 5 並 JVM 並並並並並並並並並並並並並並 (OpenJDK) 並並 並 [email protected] [email protected] Twitter @nminoru_jp

Jvm reading-parallel gc

Embed Size (px)

Citation preview

Page 1: Jvm reading-parallel gc

1

並列 GC第 5 回 JVM ソースコードリーディングの会 (OpenJDK)

中村 実[email protected]

[email protected] @nminoru_jp

Page 2: Jvm reading-parallel gc

2

OpenJDK の GC の種類オプション ヒープのクラス 空間のクラス

逐次 GC -XX:+UseSerialGC GenCollectedHeap DefNewGeneration

TenuredGeneration

並列 GC -XX:+UseParNewGC GenCollectedHeap ParNewGeneration

TenuredGeneration

並列 GC -XX:+UseParallelGC ParallelScavengeHeap

コンカレントGC

-XX:+UseConcMarkSweepGC GenCollectedHeap ParNewGeneration

ConcurrentMarkSweep

インクリメンタル GC

-Xincgc( コンカレント GC に -XX:+CMSIncrementalMode をつけたもの )

GenCollectedHeap 同上

G1GC -XX:+UseG1GC G1CollectedHeap

Page 3: Jvm reading-parallel gc

3

OpenJDK6 の GC の種類

• 並列 GC は 2 種類ある– 従来の SerialGC の NewGC を並列化した

UseParNewGC– 新世代と旧世代の配置を動的に変更

し、 NewGC の並列化を行ったUseParallelGC

• UseParallelGC は -XX:+UseParallelOldGC をつけることで Full GC の並列化も可能

– アルゴリズム的にはほとんど違いがない• 今日は UseParallelGC を読みます。

Page 4: Jvm reading-parallel gc

4

逐次 GC の簡単なおさらい

• 世代別 GC– マイナー GC(New GC) はコピー GC– メジャー GC(Full GC) はマークコンパクト

GC

Page 5: Jvm reading-parallel gc

5

逐次 GC の簡単なおさらい

• 空間配置– ヒープは New, Oldm Perm の 3 つに

分かれる。– New はさらに Eden, From, To に分か

れる。 From と To は GC 時に切り替わる。

Perm

Old Gen.

Eden

From To

Page 6: Jvm reading-parallel gc

6

逐次 GC の簡単なおさらい

• New GC– Eden と From にあるオブジェクトのう

ち生きているものを To にコピーする。– 生きているオブジェクトとは

• (a) スタックや Old 世代から参照されていもの

• (b) (a) からさらに参照を受けているもの。

– GC 後は Eden と From が空になる。 From と To はラベルを交換する。

Perm

Old Gen.

Eden

From To

Page 7: Jvm reading-parallel gc

7

New GC の手順

1. Old を下位アドレスからオブジェクトを順にスキャンする、 Eden/Fromへの参照を持つオブジェクトを探す。1. オブジェクトがまだコピーされていなけ

れば、 To 領域にコピーし、移動先にポインタを張り替える

2. オブジェクトがすでにコピーされていれば移動先にポインタを張り替える

2. To 領域に移動したオブジェクトに対しても同様のスキャンを行う。 c

b

a

Page 8: Jvm reading-parallel gc

8

New GC の手順

1. Old を下位アドレスからオブジェクトを順にスキャンする、 Eden/Fromへの参照を持つオブジェクトを探す。1. オブジェクトがまだコピーされていなけ

れば、 To 領域にコピーし、移動先にポインタを張り替える

2. オブジェクトがすでにコピーされていれば移動先にポインタを張り替える

2. To 領域に移動したオブジェクトに対しても同様のスキャンを行う。 c

b

a

b’

Page 9: Jvm reading-parallel gc

9

New GC の手順

1. Old を下位アドレスからオブジェクトを順にスキャンする、 Eden/Fromへの参照を持つオブジェクトを探す。1. オブジェクトがまだコピーされていなけ

れば、 To 領域にコピーし、移動先にポインタを張り替える

2. オブジェクトがすでにコピーされていれば移動先にポインタを張り替える

2. To 領域に移動したオブジェクトに対しても同様のスキャンを行う。 c

b

a

b’

Page 10: Jvm reading-parallel gc

10

NewGC のポイント• Forwarding pointer

– コピー元のオブジェクトの oopDesc の _mark を潰してコピー先のアドレスを記録する。(src/share/vm/oops/mark.hpp)

– すでに移動しているかどうかは LSB の 2 ビットを marked に変えて通知する。

オブジェクトの本体

_klass214hash code

markbiased lockage

Page 11: Jvm reading-parallel gc

11

ソースコードで確認

• src/share/vm/memory/defNewGeneartion.cpp:524

void DefNewGeneration::collect() {

// ルート (Old領域を含む ) 内のオブジェクトをスキャンして Eden/From空間 // を指すオブジェクトを探す gch->gen_process_strong_roots(_level,

// To 空間にコピーさえたオブジェクトをスキャンする evacuate_followers.do_void();

Page 12: Jvm reading-parallel gc

12

ソースコードで確認• DefNewGeneration::copy_to_survivor_space at

src/share/vm/memory/defNewGeneartion.cpp:720

oop DefNewGeneration::copy_to_survivor_space(oop old) { size_t s = old->size(); oop obj = NULL; // TO 領域から空き容量を確保 obj = (oop) to()->allocate(s); // コピー Copy::aligned_disjoint_words((HeapWord*)old, (HeapWord*)obj, s); // 古い方を old->forward_to(obj); return obj;}

Page 13: Jvm reading-parallel gc

13

ソースコードで確認DefNewGeneration::copy_to_survivor_space at share/vm/memory/defNewGeneration.cpp:723FastScanClosure::do_oop_work at share/vm/utilities/globalDefinitions.hpp:418FastScanClosure::do_oop_nv at share/vm/runtime/thread.hpp:1826objArrayKlass::oop_oop_iterate_nv at share/vm/oops/objArrayKlass.cpp:439oopDesc::oop_iterate(FastScanClosure*) at share/vm/utilities/growableArray.hpp:204ContiguousSpace::oop_since_save_marks_iterate_nv at share/vm/memory/space.cpp:784OneContigSpaceCardGeneration::oop_since_save_marks_iterate_nv at share/vm/memory/generation.cpp:682GenCollectedHeap::oop_since_save_marks_iterate at share/vm/memory/genCollectedHeap.cpp:786DefNewGeneration::FastEvacuateFollowersClosure::do_void at share/vm/memory/defNewGeneration.cpp:112DefNewGeneration::collect at share/vm/memory/defNewGeneration.cpp:591GenCollectedHeap::do_collection at share/vm/memory/genCollectedHeap.cpp:610GenCollectorPolicy::satisfy_failed_allocation at share/vm/memory/collectorPolicy.cpp:694GenCollectedHeap::satisfy_failed_allocation at share/vm/memory/genCollectedHeap.cpp:706VM_GenCollectForAllocation::doit at share/vm/gc_implementation/shared/vmGCOperations.cpp:165VM_Operation::evaluate at share/vm/runtime/vm_operations.cpp:65VMThread::evaluate_operation at share/vm/runtime/vmThread.cpp:360VMThread::loop at share/vm/runtime/vmThread.cpp:466VMThread::run at share/vm/runtime/vmThread.cpp:273java_start at os/linux/vm/os_linux.cpp:852start_thread from /lib/libpthread.so.0clone from /lib/libc.so.6

Page 14: Jvm reading-parallel gc

14

逐次 GC の簡単なおさらい

• Full GC– ヒープ全体をマーキングして、生

きているオブジェクトを Old に集める。

– 生きているオブジェクトが Old に入りきらない場合には各空間でコンパクションする

Perm

Old Gen.

Eden

From To

Page 15: Jvm reading-parallel gc

15

Full GC の手順• Phase1: marking

– スタックから参照されているオブジェクトをトレースしてマーキングする。

– vm/gc_imimplementation/shared/markSweep.inline.hpp

• Phase2: compute new address– Old 空間内をコンパクションした場合の移動先を計算し、 forwarding

pointer として書き込む• Phase3: adjust pointers

– オブジェクトの参照 ( ポインタ ) を移動先のアドレスに書き換える• Phase4: compaction(move)

– 生きているオブジェクトだけを詰めて移動する。

• GenMarkSweep::invoke_at_safepoint at src/share/vm/memory/genMarkSweep.cpp:59

Page 16: Jvm reading-parallel gc

16

ここから並列 GC の話

Page 17: Jvm reading-parallel gc

17

UseParallelGC

• GC スレッドの並列化– GCTaskThread はシステムの CPU 数に合わ

せて生成される(-XX:ParallelGCThreads=nで変更可能 )

– VMThread が GC を受けて、 GCTaskThreadに処理を依頼する。

• システムに一つのタスクキューに GCTask の派生型の処理を push すると、 GCTaskThread がpop して処理を開始する。

• parallelScavenge/gcTaskThread.cpp:95

Page 18: Jvm reading-parallel gc

18

UseParallelGC

• VM_ParallelGCSystemGC::doit(hotspot/src/share/vm/gc_implementation/parallelScavenge/vmPSOperations.cpp) の中で GC を発生させる。

// NewGC の場合ParallelScavengeHeap::invoke_scavenge PSScavenge::invoke() -> PSScavenge::invoke_no_policy (本体 )

// Full GC の場合ParallelScavengeHeap::invoke_full_gc if (UseParallelOldGC) PSParallelCompact::invoke -> PSParallelCompact::invoke_no_policy (本体 ) else PSMarkSweep::invoke

Page 19: Jvm reading-parallel gc

19

Parallel Scavange

• New GC の並列版– New GC のほぼ全処理が並列化される– ランデブーポイントは 1 箇所

• LAB– オブジェクトを TO 領域にコピーする際に、 TLAB のようにあら

かじめ一定範囲を GCTaskThread に割り当てる機構。アロケートのためのロックを減らせるのと、キャッシュの false sharing を防げる。

• PSPromotionManager– GC スレッド毎にインスタンスがあり、マーキングスタックや

LAB を管理。• 自分の仕事を先に済ませた GC スレッドは、他の GC スレッド

の担当分を盗める (steal) ようになている。• OldToYoungRootsTask

– Old 領域は複数の GC スレッドで処理するため 128 バイト単位でストライプ状に担当領域を分ける。

Page 20: Jvm reading-parallel gc

20

Parallel Scavange

• Parallel Scavange のスタートポイントPSScavenge::invoke_no_policy() のスタックトレース

#0 PSScavenge::invoke_no_policy at parallelScavenge/psScavenge.cpp:250#1 PSParallelCompact::invoke at parallelScavenge/psParallelCompact.cpp:1980#2 ParallelScavengeHeap::invoke_full_gc at share/vm/utilities/growableArray.hpp:204#3 VM_ParallelGCSystemGC::doit at parallelScavenge/vmPSOperations.cpp:101#4 VM_Operation::evaluate at share/vm/runtime/vm_operations.cpp:65#5 VMThread::evaluate_operation at share/vm/runtime/vmThread.cpp:360#6 VMThread::loop at share/vm/runtime/vmThread.cpp:466#7 VMThread::run at /share/vm/runtime/vmThread.cpp:273#8 java_start at os/linux/vm/os_linux.cpp:852#9 start_thread from /lib/libpthread.so.0#10 clone from /lib/libc.so.6

Page 21: Jvm reading-parallel gc

21

Parallel Scavange

• Old 世代のスキャン関数CardTableExtension::scavenge_contents_parallel

#0 CardTableExtension::scavenge_contents_parallel at parallelScavenge/cardTableExtension.cpp:227#1 OldToYoungRootsTask::do_it at parallelScavenge/psTasks.cpp:224#2 GCTaskThread::run at parallelScavenge/gcTaskThread.cpp:135#3 java_start at os/linux/vm/os_linux.cpp:852#4 start_thread from /lib/libpthread.so.0#5 clone from /lib/libc.so.6

Page 22: Jvm reading-parallel gc

22

Parallel Scavange

• 一番肝になる関数 PSPromotionManager::copy_to_survivor_space が呼び出されるまでのスタックトレース

#0 PSPromotionManager::copy_to_survivor_space at parallelScavenge/psPromotionManager.cpp:259#1 PSScavenge::copy_and_push_safe_barrier at vm/utilities/growableArray.hpp:204#2 PSScavengeRootsClosure::do_oop_work at vm/utilities/growableArray.hpp:204#3 PSScavengeRootsClosure::do_oop at vm/utilities/growableArray.hpp:204#4 Universe::oops_do at vm/memory/universe.cpp:262#5 ScavengeRootsTask::do_it at parallelScavenge/psTasks.cpp:75#6 GCTaskThread::run at parallelScavenge/gcTaskThread.cpp:135#7 java_start at os/linux/vm/os_linux.cpp:852#8 start_thread from /lib/libpthread.so.0#9 clone from /lib/libc.so.6

Page 23: Jvm reading-parallel gc

23

Parallel Scavangeoop PSPromotionManager::copy_to_survivor_space(oop o) { oop new_obj = NULL; markOop test_mark = o->mark(); if (!test_mark->is_marked()) { // このオブジェクトはまだコピーされていない。 // LAB からコピー先確保 new_obj = (oop) _young_lab.allocate(new_obj_size); // new_obj が NULL なら例外処理 Copy::aligned_disjoint_words((HeapWord*)o, (HeapWord*)new_obj, new_obj_size); // Compare-and-swap でコピー元オブジェクトに foward pointer を設定 if (o->cas_forward_to(new_obj, test_mark)) { new_obj->push_contents(this); } else { // CAS に失敗した場合、コピーしたオブジェクトは無駄になった。 // unallocate_object(new_obj) で LAB に返す。 new_obj = o->forwardee(); } } else new_obj = o->forwardee(); // 誰かが既にマークしている場合 return new_obj;}

Page 24: Jvm reading-parallel gc

24

Parallel Scavange

• タスクスチール– マーキングスタックは share/vm/utilities/

taskqueue.hpp のデータ構造でできている。これはロックフリーデータ構造 http://www.nminoru.jp/~nminoru/programming/arora_dequeue.html

– StealTask::StealTask

- TaskQueueSuper<N> - GenericTaskQueue<E, N> - OverflowTaskQueue<E, N> - OopStarTaskQueue

Page 25: Jvm reading-parallel gc

25

Parallel Compact

• Full GC の並列版– Mark & compact– Bitmap marking を行っている– 移動先のデータを forwarding pointer を使わず

に、 ParallelCompactData に記録する• 手順が少し異なる

– Phase 1: marking(parallel)– Phase 2: summary(serial)

• compute new address に相当

– Phase 3: adjust roots(serial)• スタック等の参照を変更する。ヒープ上のオブジェクトの pointer

adjust は行わない。– Phase 4: compact perm(serial)– Phaes 5: compact(parallel)

Page 26: Jvm reading-parallel gc

26

Parallel Compact

• Parallel Compact のスタートポイントPSScavenge::invoke_no_policy Full のスタックトレース

#0 PSScavenge::invoke_no_policy at parallelScavenge/psScavenge.cpp:250#1 PSParallelCompact::invoke at parallelScavenge/psParallelCompact.cpp:1980#2 ParallelScavengeHeap::invoke_full_gc at share/vm/utilities/growableArray.hpp:204#3 VM_ParallelGCSystemGC::doit at parallelScavenge/vmPSOperations.cpp:101#4 VM_Operation::evaluate at share/vm/runtime/vm_operations.cpp:65#5 VMThread::evaluate_operation at share/vm/runtime/vmThread.cpp:360#6 VMThread::loop at share/vm/runtime/vmThread.cpp:466#7 VMThread::run at share/vm/runtime/vmThread.cpp:273#8 java_start at os/linux/vm/os_linux.cpp:852#9 start_thread from /lib/libpthread.so.0#10 clone from /lib/libc.so.6

Page 27: Jvm reading-parallel gc

27

Parallel Compact

• Phase1 marking で未マークなオブジェクトを見つけてPerMarkBitMap を打つところまでのスタックトレース

#0 ParMarkBitMap::mark_obj at parallelScavenge/parMarkBitMap.cpp:92#1 ParMarkBitMap::mark_obj at share/vm/memory/cardTableRS.hpp:150#2 PSParallelCompact::mark_obj at share/vm/memory/cardTableRS.hpp:150#3 PSParallelCompact::mark_and_push at share/vm/memory/cardTableRS.hpp:150#4 PSParallelCompact::MarkAndPushClosure::do_oop at parallelScavenge/psParallelCompact.cpp:822#5 JNIHandles::oops_do at share/vm/runtime/jniHandles.cpp:146#6 MarkFromRootsTask::do_it at parallelScavenge/pcTasks.cpp:88#7 GCTaskThread::run at parallelScavenge/gcTaskThread.cpp:135#8 java_start at os/linux/vm/os_linux.cpp:852#9 start_thread from /lib/libpthread.so.0#10 clone from /lib/libc.so.6

Page 28: Jvm reading-parallel gc

28

Parallel Compact

• Phase1 marking で未マークなオブジェクトを見つけてPerMarkBitMap を打つところまでのスタックトレース

#0 ParMarkBitMap::mark_obj at parallelScavenge/parMarkBitMap.cpp:92#1 ParMarkBitMap::mark_obj at share/vm/memory/cardTableRS.hpp:150#2 PSParallelCompact::mark_obj at share/vm/memory/cardTableRS.hpp:150#3 PSParallelCompact::mark_and_push at share/vm/memory/cardTableRS.hpp:150#4 PSParallelCompact::MarkAndPushClosure::do_oop at parallelScavenge/psParallelCompact.cpp:822#5 JNIHandles::oops_do at share/vm/runtime/jniHandles.cpp:146#6 MarkFromRootsTask::do_it at parallelScavenge/pcTasks.cpp:88#7 GCTaskThread::run at parallelScavenge/gcTaskThread.cpp:135#8 java_start at os/linux/vm/os_linux.cpp:852#9 start_thread from /lib/libpthread.so.0#10 clone from /lib/libc.so.6

Page 29: Jvm reading-parallel gc

29

Parallel Compact

Phase3 adjust rootsPSParallelCompact::adjust_pointer のスタックトレース

#0 PSParallelCompact::adjust_pointer at parallelScavenge/psParallelCompact.hpp:1318#1 PSParallelCompact::AdjustPointerClosure::do_oop at parallelScavenge/psParallelCompact.cpp:817#2 Universe::oops_do at share/vm/memory/universe.cpp:208#3 PSParallelCompact::adjust_roots at parallelScavenge/psParallelCompact.cpp:2449#4 PSParallelCompact::invoke_no_policy at parallelScavenge/psParallelCompact.cpp:2098#5 PSParallelCompact::invoke at parallelScavenge/psParallelCompact.cpp:1987#6 ParallelScavengeHeap::invoke_full_gc at share/vm/utilities/growableArray.hpp:204#7 VM_ParallelGCSystemGC::doit at parallelScavenge/vmPSOperations.cpp:101...

Page 30: Jvm reading-parallel gc

30

Parallel Compact

PSParallelCompact::adjust_pointer at parallelScavenge/psParallelCompact.hpp:1318

template <class T> inline void PSParallelCompact::adjust_pointer(T* p, bool isroot) { T heap_oop = oopDesc::load_heap_oop(p); if (!oopDesc::is_null(heap_oop)) { oop obj = oopDesc::decode_heap_oop_not_null(heap_oop); oop new_obj = (oop)summary_data().calc_new_pointer(obj); // 移動先の取得 // Just always do the update unconditionally? if (new_obj != NULL) { oopDesc::encode_store_heap_oop_not_null(p, new_obj); // ポインタ変更 } }}

Page 31: Jvm reading-parallel gc

31

Parallel Compact

• Phase4

#0 PSParallelCompact::adjust_pointer at parallelScavenge/psParallelCompact.hpp:1318#1 PSParallelCompact::adjust_pointer at share/vm/memory/cardTableRS.hpp:150#2 klassKlass::oop_update_pointers at share/vm/oops/klassKlass.cpp:193#3 oopDesc::update_contents at share/vm/utilities/growableArray.hpp:204#4 UpdateOnlyClosure::do_addr at parallelScavenge/psParallelCompact.hpp:1498#5 UpdateOnlyClosure::do_addr at parallelScavenge/psParallelCompact.cpp:3514#6 ParMarkBitMap::iterate at parallelScavenge/parMarkBitMap.cpp:219#7 ParMarkBitMap::iterate at share/vm/utilities/growableArray.hpp:204#8 PSParallelCompact::update_and_deadwood_in_dense_prefix at parallelScavenge/psParallelCompact.cpp:3001#9 PSParallelCompact::move_and_update at parallelScavenge/psParallelCompact.cpp:3404#10 PSParallelCompact::compact_perm at sparallelScavenge/psParallelCompact.cpp:2484#11 PSParallelCompact::invoke_no_policy at parallelScavenge/psParallelCompact.cpp:2103

Page 32: Jvm reading-parallel gc

32

Parallel Compact

• Phase5#0 PSParallelCompact::adjust_pointer at share/vm/memory/cardTableRS.hpp:150#1 PSParallelCompact::adjust_pointer at share/vm/memory/cardTableRS.hpp:150#2 instanceKlass::oop_update_pointers at share/vm/oops/instanceKlass.cpp:1870#3 oopDesc::update_contents at share/vm/utilities/growableArray.hpp:204#4 MoveAndUpdateClosure::do_addr at parallelScavenge/psParallelCompact.cpp:3494#5 ParMarkBitMap::iterate at parallelScavenge/parMarkBitMap.cpp:171#6 ParMarkBitMap::iterate at share/vm/utilities/growableArray.hpp:204#7 PSParallelCompact::fill_region at parallelScavenge/psParallelCompact.cpp:3323#8 PSParallelCompact::fill_and_update_region at share/vm/utilities/growableArray.hpp:204#9 ParCompactionManager::drain_region_stacks at parallelScavenge/psCompactionManager.cpp:172#10 DrainStacksCompactionTask::do_it at parallelScavenge/pcTasks.cpp:297#11 GCTaskThread::run at parallelScavenge/gcTaskThread.cpp:135...