これは、作者の同意を得て発行されたAlexey Shipilevによる記事「Do It Yourself(OpenJDK)Garbage Collector」の翻訳です。 タイプミスやその他のバグをPMに報告してください-修正します。
ランタイムで何かを作成するプロセスは楽しい練習です。 少なくとも最初のバージョンの作成! 動作を便利に監視およびデバッグできる、信頼性が高く、高性能でフェイルセーフなランタイムサブシステムを構築することは、非常に困難な作業です。
単純なガベージコレクターを作成するのは一見単純です。この記事では、これを実行したいと考えています。 FOSDEM 2019のRoman Kennkeは、このパッチの以前のバージョンを使用して、 「20分間でGCを作成する」というタイトルの講演とデモを行いました。 そこに実装されたコードは多くのことを実証し、十分にコメントされているという事実にもかかわらず、何が起こっているのかについての高レベルの説明が必要です-これがこの記事の登場です。
ガベージコレクターの作業の基本的な理解は、ここに記載されている内容を理解するのに非常に役立ちます。 この記事では、HotSpotの特定の実装で仕様とアイデアを使用しますが、ここではGC設計に関する入門コースはありません。 GCハンドブックを読んで、GCの基本に関する最初の章を読んでください 。さらに速くなると、ウィキペディアの記事が始まります 。
内容
1. GCの構成
さまざまなGCが記述されたので、独自のGCを作成するのは非常に簡単です。すでに記述されている多くの要素を使用して、実装の詳細に関する懸念の一部を実証済みのテスト済みのコードに移行できます
1.1。 イプシロンGC
OpenJDK 11では、新しいJEP 318: Epsilon:A No-Op Garbage Collector(Experimental)が導入されました。 そのタスクは、メモリの解放が不要な場合や禁止されている場合でも、最小限の実装を提供することです。 JEPでは、なぜ有用なのかについて詳しく説明します。
実装の観点から、「ガベージコレクター」は悪い名前です。メモリの割り当てと解放の両方を担当する「自動メモリマネージャー」という用語を使用する方が正しいでしょう。 Epsilon GCは「割り当て」のみを実装し、「リリース」は一切処理しません。 したがって、Epsilon GCを使用して、「リリース」アルゴリズムの実装をゼロから開始できます。
1.1.1。 メモリ割り当て
Epsilon GCの最も開発された部分は、 メモリの割り当てを担当します 。 任意のサイズのメモリを割り当て、必要なサイズのスレッドローカル割り当てバッファ(TLAB)を作成するための外部リクエストを処理します。 実装自体は、空きメモリがなく、誰も失われたバイトを返さないため、TLABを過度に拡張しないようにしています。
1.1.2。 障壁
ガベージコレクターの中には、GC不変条件を維持するためにアプリケーションとの対話を必要とするものがあり、ヒープにアクセスしようとするとランタイムとアプリケーションがいわゆるバリアを作成するように強制します。 これは、すべてのマルチスレッドコレクターだけでなく、世代を持ち世界を止めている多くのコレクターにも当てはまります。
Epsilonはバリアを必要としませんが、ランタイムとコンパイラーはバリアが何もしないことをまだ知りたいです。 いつでもどこでもそれを処理するのは面倒です。 幸いなことに、OpenJDK 11以降、新しいJEP-304があります。「Garbage Collection Interface」です 。これにより、バリアの挿入がはるかに簡単になります。 特に、 Epsilonで設定されたバリアは空であり、保存、読み込み、CAS、arraycopyなどの簡単な作業はすべて、既存のスーパークラスから簡単なバリアの実装に委任できます。 バリアも必要としないGCを作成している場合は、Epsilonのコードを簡単に再利用できます。
1.1.3。 接続の監視
GC実装の最後の退屈な部分は、JVM内の一連の監視メカニズムへのフックです。MXビン、診断コマンドなどが機能するはずです。 イプシロンはすでにこれをすべて行っています。
1.2。 ランタイムとGC
1.2.1。 ルート要素
一般に、ガベージコレクタは、Javaランタイムのヒープ参照を正確に知る必要があります。 GCルートと呼ばれるこれらのルート要素は、ストリームスタックとローカル変数(JITコンパイルコードにあるものを含む!)、ネイティブクラスとクラスローダー、JNIの参照などのスロットになります。 これらの要素を特定する試みは、非常に複雑で退屈です。 しかし、ホットスポットでは、それらはすべて適切なVMサブシステムを使用して追跡されるため、既存のGC実装がそれらとどのように機能するかを簡単に知ることができます。 さらに本文でそれを見るでしょう。
1.2.2。 オブジェクトのクロール
ガベージコレクターは、Javaオブジェクトのアウトバウンドリンクをバイパスする必要があります。 この操作はどこにでもあるので、ランタイムの共通部分は既成の回避策を提供します;自分で何かを書く必要はありません。 以下に特定の実装を含むセクションがあります。たとえば、呼び出しobj→oop_iterate
見つけることができます。
1.2.3。 変位
移動するガベージコレクタは、移動したオブジェクトの新しいアドレスをどこかに書き留める必要があります。 この転送データを書き込むことができる場所はいくつかあります。
- オブジェクト自体 (シリアル、パラレルなど)で「マーカーワード」を再利用できます 。 ワールドが停止すると、オブジェクトへのすべてのアクセスが制御され、マーカーワードに入力することにした一時データをJavaスレッドが表示できないことが保証されます。 転送データを保存するために再利用できます。
- 別のネイティブ移動テーブル( ZGC 、C4など)を維持できます。 これにより、GCはランタイムとアプリケーションの残りの部分から完全に分離されます。これは、GCだけがそのようなテーブルの存在を知っているためです。 競争力のあるアセンブラーは通常、このようなスキームを使用します-彼らは不必要な問題の束で苦しむことを望みません。
- オブジェクトに別の単語を追加できます( シェナンドアなど)。 前述の2つのアプローチのこの組み合わせにより、ランタイムとアプリケーションが既存のヘッダーを問題なく使用できるようになるだけでなく、転送データも節約されます。
1.2.4。 マーカーデータ
ガベージコレクターは、 マーキングデータをどこかに書き込む必要があります。 繰り返しますが、それらを保存するにはいくつかの方法があります。
- オブジェクト自体(シリアル、パラレルなど)でマーカーワードを再利用できます。 繰り返しますが、ワールドストップモードでは、マーカーワードのビットを使用して、タグのファクトをエンコードできます。 さらに、すべての生きているオブジェクトを移動する必要がある場合は、ヒープに沿ってオブジェクトごとに移動します。これは、 ヒープが解析可能であるために可能です。
- マーキングデータ(G1、Shenandoahなど)を保存するための別の構造を維持できます。 これは通常、ヒープのNバイトごとにカードの1ビットにマップする個別のビットマップを使用して行われます。 通常、Javaオブジェクトは8バイトで整列されるため、カードはヒープから64ビットごとにカードの1ビットにマップし、ネイティブメモリのヒープサイズの1/64を占有します。 これらのオーバーヘッドは、ヒープをスキャンして生きているオブジェクト、特にスパースオブジェクトの存在を確認する場合に効果があります。マップをバイパスすることは、オブジェクトごとにソートされるヒープをバイパスするよりもはるかに高速です。
- ラベルをリンク自体にエンコードします(ZGC、C4など)。 これには、アプリケーションとの調整が必要です。リンクからこれらのラベルをすべて削除するか、正確性を維持するために他のいくつかのトリックを実行する必要があります。 つまり、GCからの障壁または追加の作業が必要です。
2.一般計画
最も可能性が高いのは、Epsilonの上に実装するのが最も簡単なのは、LISP2スタイルのMark-Compactです。 このGCの基本的な考え方は、WikipediaとGCハンドブック (3.2章)の両方で説明されています。 アルゴリズムのスケッチは、以下の実装に関するセクションにありますが、ウィキペディアまたはGCハンドブックを読んで、何をしようとしているのかを理解することを強くお勧めします。
問題のアルゴリズムは、GCのシフトです。移動するオブジェクトは、ヒープ内の先頭に配列で移動します。 長所と短所があります。
- メモリ割り当ての順序を維持します。 これは、メモリ内のレイアウトを制御するのに非常に適しています(あなたにとって重要な場合は、コントロールのフリーク、あなたの時間です!)。 欠点は、この方法では自動リンクローカリティが得られないことです。
- その複雑さは、オブジェクト数のO(N)です。 ただし、直線性には代償が伴います。GCは、ビルドサイクルごとに4回の束をバイパスする必要があります。
- ヒープ上の空きメモリは必要ありません! 生きているオブジェクトを退避するためにヒープ上のメモリを予約する必要はありません。したがって、99でオーバーフローしたヒープで作業することもできます。(9)%。 単純なコレクターの他のアイデア、たとえば、セミスペースを持つスカベンジャー(セミスペーススカベンジャー)を取り上げる場合、ヒープの表示をわずかに書き直して、避難用のスペースを少し確保する必要がありますが、これはこの演習の範囲外です。
- 問題に少し取り組むと、GCが非アクティブな期間中にメモリと時間の消費をゼロにすることができます。 任意の状態のメモリで開始し、停止して、大幅に圧縮します。 これは、Epsilonの動作に非常によく適合します。最後のオブジェクトの直後に強調表示を続けるだけです。 これはマイナスでもあります。ヒープの先頭にあるいくつかの死んだオブジェクトは、多数の動きをもたらします。
- 新しいバリアを必要としないだけで、
EpsilonBarrierSet
そのまま再利用できます。
簡単にするために、GCの実装では世界の完全な停止(stop-the-world、STW)を使用し、世代もマルチスレッドもありません。 この場合、ビットマップを使用してマークを保存し、マーカーワードを再利用して移動データを保存するのが理にかなっています。
3. GCコアの実装
実装全体を読んで理解することは、無知な人にとっては複雑すぎるかもしれません。 このセクションでは、ステップごとに説明します。
3.1。 プロローグ
通常、ガベージコレクターは、ビルドの準備としていくつかのことを行う必要があります。 コメントを読んでください、彼らは彼ら自身のために話すべきです:
{ GCTraceTime(Info, gc) time("Step 0: Prologue", NULL); // . // : , , // «» , // , . if (!os::commit_memory((char*)_bitmap_region.start(), _bitmap_region.byte_size(), false)) { log_warning(gc)("Could not commit native memory for marking bitmap, GC failed"); return; } // , , // TLAB-. ensure_parsability(true); // , GC. CodeCache::gc_prologue(); BiasedLocking::preserve_marks(); // . // . DerivedPointerTable::clear(); }
ビットマップを使用してオブジェクトの到達可能性を追跡するため、使用する前にビットマップをクリアする必要があります。 または、私たちの場合、GCサイクルを開始する前にリソースを要求しないことを目指しているため、事前にビットマップをメモリにコミットする必要があります。 これにより、少なくともLinuxでは、特にスパースヒープの場合、ビットマップのほとんどがページゼロをポイントするため、いくつかの興味深い利点があります。
スレッドはTLABを解放し、ビルドの完了後にGCに新しいものを要求する必要があります。
TLABとjava.lang.ThreadLocal
混同しないでください。 GCの観点からは、ThreadLocalは通常のオブジェクトであり、Javaコードで特に必要とされない限り、GCによってコンパイルされません。
ランタイムの一部、特にJavaヒープへのリンクを保持している部分は、ガベージコレクション中に破損するため、GCがすぐに動作し始めることを特に警告する必要があります。 これにより、GCが移動する前に、各サブシステムが状態の一部を準備して保存できるようになります。
3.2。 マーキング
世界のストップモードでのマーキングは、ほとんどすべてが既に行われている場合、非常に簡単になります。 ラベル付けは非常に標準的であり、多くの実装では、GCが最初のステップである可能性が高いです。
{ GCTraceTime(Info, gc) time("Step 1: Mark", NULL); // , . // , , // . EpsilonMarkStack stack; EpsilonScanOopClosure cl(&stack, &_bitmap); // . process_roots(&cl); stat_reachable_roots = stack.size(); // , . // , , // . while (!stack.is_empty()) { oop obj = stack.pop(); obj->oop_iterate(&cl); stat_reachable_heap++; } // . DerivedPointerTable::set_active(false); }
これは、他のグラフとまったく同じように機能します。最初の到達可能な頂点のセットでトラバーサルを開始し、出力エッジに沿って進み、訪問したすべての頂点を記録します。 ツアーは、未訪問のピークがすべて終わるまで続きます。 GCでは、「頂点」はオブジェクトであり、「エッジ」はそれらの間のリンクです。
技術的には、オブジェクトのグラフを再帰的にたどることができますが、これは非常に大きな直径を持つことができる任意のグラフにとっては悪い考えです。 10億のピークのリンクリストを想像してください! したがって、再帰の深さを制限するために、検出されたオブジェクトを記録するマーキングスタックを使用します。
到達可能なオブジェクトの初期セットはGCルートです。 ここで、 process_roots
について詳しくprocess_roots
しません。詳細については後で説明します。 今のところ、VM側からのすべての到達可能なリンクをバイパスしているとだけ言っておきましょう。
マーク付きのビットマップは、 マーキング波面 (既に訪れた多くのオブジェクト)を記録するツールとして機能し、最終的には、目的の結果のリポジトリ、到達可能なすべてのオブジェクトのセットとして機能します。 実際の作業はEpsilonScanOopClosure
で行われ、すべての興味深いオブジェクトに適用され、選択したオブジェクトのすべてのリンクでEpsilonScanOopClosure
れます。
見てください、Javaはファッショナブルになる前に閉じることができました!
class EpsilonScanOopClosure : public BasicOopIterateClosure { private: EpsilonMarkStack* const _stack; MarkBitMap* const _bitmap; template <class T> void do_oop_work(T* p) { // p - , oop, // , : T o = RawAccess<>::oop_load(p); if (!CompressedOops::is_null(o)) { oop obj = CompressedOops::decode_not_null(o); // . , . , // . // +, // . if (!_bitmap->is_marked(obj)) { _bitmap->mark((HeapWord*)obj); _stack->push(obj); } } } };
この手順を完了すると、 _bitmap
にはライブオブジェクトの場所を示すビットが含まれます。 これにより、すべての生きているオブジェクトをバイパスすることができます。例えば:
// . // , ( ) , // 1/64 . void EpsilonHeap::walk_bitmap(ObjectClosure* cl) { HeapWord* limit = _space->top(); HeapWord* addr = _bitmap.get_next_marked_addr(_space->bottom(), limit); while (addr < limit) { oop obj = oop(addr); assert(_bitmap.is_marked(obj), "sanity"); cl->do_object(obj); addr += 1; if (addr < limit) { addr = _bitmap.get_next_marked_addr(addr, limit); } } }
3.3。 新しい住所を計算する
これも非常に簡単な手順であり、アルゴリズムの説明どおりに実装されます。
// forwarding data (, ) // . . // . PreservedMarks preserved_marks; // GC. HeapWord* new_top; { GCTraceTime(Info, gc) time("Step 2: Calculate new locations", NULL); // , // . , - . EpsilonCalcNewLocationObjectClosure cl(_space->bottom(), &preserved_marks); walk_bitmap(&cl); // . // , // , "" // . new_top = cl.compact_point(); stat_preserved_marks = preserved_marks.size(); }
目を引く唯一のことは、Javaオブジェクトのマーキングワードに新しいアドレスを格納することを決定したことです。このワードは、たとえばロックに関する情報など、重要なものですでに占有されている可能性があります。 幸いなことに、このような重要な単語は非常にまれであり、必要な場合は単純に個別に保存できます。これがPreservedMarks
使用目的です。
実際のアルゴリズムの作業は、 EpsilonCalcNewLocationObjectClosure
によって行われEpsilonCalcNewLocationObjectClosure
。
class EpsilonCalcNewLocationObjectClosure : public ObjectClosure { private: HeapWord* _compact_point; PreservedMarks* const _preserved_marks; public: EpsilonCalcNewLocationObjectClosure(HeapWord* start, PreservedMarks* pm) : _compact_point(start), _preserved_marks(pm) {} void do_object(oop obj) { // : . // ( , // ), , // . if ((HeapWord*)obj != _compact_point) { markOop mark = obj->mark_raw(); if (mark->must_be_preserved(obj)) { _preserved_marks->push(obj, mark); } obj->forward_to(oop(_compact_point)); } _compact_point += obj->size(); } HeapWord* compact_point() { return _compact_point; } };
forward_to
は、オブジェクトのマーカーワードに「移動アドレス」を格納するため、最も重要な部分です。 これは次のステップで必要になります。
3.4。 ポインターを修正する
ここで、ヒープをもう一度調べて、次のアルゴリズムに従ってすべてのリンクを新しいアドレスで書き換える必要があります。
{ GCTraceTime(Info, gc) time("Step 3: Adjust pointers", NULL); // _ _, // « ». forwarding data, // . . EpsilonAdjustPointersObjectClosure cl; walk_bitmap(&cl); // , VM, // : . EpsilonAdjustPointersOopClosure cli; process_roots(&cli); // , , // . preserved_marks.adjust_during_full_gc(); }
シフトされたオブジェクトへの参照には、ヒープ自体のオブジェクトからの発信、またはGCルートからの発信の2種類があります。 両方のリンククラスを更新する必要があります。 一部の保存ラベルにはオブジェクトへの参照も保存されるため、更新するように依頼する必要があります。 PreservedMarks
は、これを行う方法を知っています。これは、オブジェクトのマーキングワードで、保存した場所と同じ場所に「データを転送する」ことを想定しているためです。
クロージャーは2つのタイプに分けられます。オブジェクトを取得してコンテンツをバイパスするものと、これらのアドレスを更新するものがあります。 ここで、パフォーマンスの小さな最適化を行うことができます。オブジェクトが移動しない場合、いくつかのレコードをまとめて保存できます。
class EpsilonAdjustPointersOopClosure : public BasicOopIterateClosure { private: template <class T> void do_oop_work(T* p) { // p - , oop. // , : T o = RawAccess<>::oop_load(p); if (!CompressedOops::is_null(o)) { oop obj = CompressedOops::decode_not_null(o); // . // , . if (obj->is_forwarded()) { oop fwd = obj->forwardee(); assert(fwd != NULL, "just checking"); RawAccess<>::oop_store(p, fwd); } } } }; class EpsilonAdjustPointersObjectClosure : public ObjectClosure { private: EpsilonAdjustPointersOopClosure _cl; public: void do_object(oop obj) { // , : obj->oop_iterate(&_cl); } };
このステップを完了した後、基本的に束を壊しました。リンクは、オブジェクトがまだ存在しない「間違った」アドレスを指します。 それを修正しましょう!
3.5。 オブジェクトを移動します
アルゴリズムに従って、オブジェクトを新しいアドレスに移動する時間:
ヒープを再度EpsilonMoveObjectsObjectClosure
し、すべての生きているオブジェクトにEpsilonMoveObjectsObjectClosure
クロージャーを適用します。
{ GCTraceTime(Info, gc) time("Step 4: Move objects", NULL); // . // . EpsilonMoveObjectsObjectClosure cl; walk_bitmap(&cl); stat_moved = cl.moved(); // , // «» . _space->set_top(new_top); }
その直後に、圧縮ポイントヒープのヒープをドラッグして、ガベージコレクションサイクルが終了した直後に、この場所からメモリを直接割り当てることができます。
シフトアセンブリでは、既存のオブジェクトの内容を上書きできますが、スキャンは同じ方向に進むため、上書きされたオブジェクトは既に適切な場所にコピーされていることに注意してください。
同じ施設の古い場所と新しい場所が交差する場合があります。 たとえば、100バイトのオブジェクトを8バイト移動した場合。 コピー手順はそれ自体でうまくいくはずであり、交差するコンテンツを正しくコピーする必要がありますCopy::aligned_*conjoint*_words
注意してCopy::aligned_*conjoint*_words
。
クロージャ自体は、移動されたオブジェクトを新しいアドレスに移動するだけです。
class EpsilonMoveObjectsObjectClosure : public ObjectClosure { public: void do_object(oop obj) { // , . - , // - mark word, // forwarding data. if (obj->is_forwarded()) { oop fwd = obj->forwardee(); assert(fwd != NULL, "just checking"); Copy::aligned_conjoint_words((HeapWord*)obj, (HeapWord*)fwd, obj->size()); fwd->init_mark_raw(); } } };
3.6。 エピローグ
ガベージコレクションが終了し、ヒープはほぼ一貫した状態になり、最後の最後の仕上げが残ります。
{ GCTraceTime(Info, gc) time("Step 5: Epilogue", NULL); // . preserved_marks.restore(); // , . DerivedPointerTable::update_pointers(); BiasedLocking::restore_marks(); CodeCache::gc_epilogue(); JvmtiExport::gc_epilogue(); // . if (!os::uncommit_memory((char*)_bitmap_region.start(), _bitmap_region.byte_size())) { log_warning(gc)("Could not uncommit native memory for marking bitmap"); } // , . // . if (EpsilonUncommit) { _virtual_space.shrink_by((_space->end() - new_top) * HeapWordSize); _space->set_end((HeapWord*)_virtual_space.high()); } }
ランタイムの残りの部分には、アセンブリ後の手順を開始する必要があることを通知します。 以前に保存した特別なマーカーワードを復元します。 マーカーカードに別れを告げる-もう必要ありません。
また、本当に必要な場合は、割り当て用のメモリを新しいサイズに減らして、オペレーティングシステムにメモリを返すことができます。
4. GCをVMに接続します
4.1。 ルートトラバーサル
VMから特別な到達可能なリンクをバイパスする必要があることを覚えていますか? 各特別なVMサブシステムに、他のJavaオブジェクトから隠されたリンクをバイパスするように依頼できます。 現在のホットスポットのこのようなルート要素の完全なリストは、次のようになります。
void EpsilonHeap::do_roots(OopClosure* cl) { // , 1 . StrongRootsScope scope(1); // . CLDToOopClosure clds(cl, ClassLoaderData::_claim_none); MarkingCodeBlobClosure blobs(cl, CodeBlobToOopClosure::FixRelocations); // . // . { MutexLockerEx lock(CodeCache_lock, Mutex::_no_safepoint_check_flag); CodeCache::blobs_do(&blobs); } { MutexLockerEx lock(ClassLoaderDataGraph_lock); ClassLoaderDataGraph::cld_do(&clds); } Universe::oops_do(cl); Management::oops_do(cl); JvmtiExport::oops_do(cl); JNIHandles::oops_do(cl); WeakProcessor::oops_do(cl); ObjectSynchronizer::oops_do(cl); SystemDictionary::oops_do(cl); Threads::possibly_parallel_oops_do(false, cl, &blobs); }
, . GC .
4.2。
GC , VM . Hotspot VM_Operation
, GC VM- :
// VM_operation, class VM_EpsilonCollect: public VM_Operation { private: const GCCause::Cause _cause; EpsilonHeap* const _heap; static size_t _last_used; public: VM_EpsilonCollect(GCCause::Cause cause) : VM_Operation(), _cause(cause), _heap(EpsilonHeap::heap()) {}; VM_Operation::VMOp_Type type() const { return VMOp_EpsilonCollect; } const char* name() const { return "Epsilon Collection"; } virtual bool doit_prologue() { // , . // GC, // . // , // . , // 1%, , , // . Heap_lock->lock(); size_t used = _heap->used(); size_t capacity = _heap->capacity(); size_t allocated = used > _last_used ? used - _last_used : 0; if (_cause != GCCause::_allocation_failure || allocated > capacity / 100) { return true; } else { Heap_lock->unlock(); return false; } } virtual void doit() { _heap->entry_collect(_cause); } virtual void doit_epilogue() { _last_used = _heap->used(); Heap_lock->unlock(); } }; size_t VM_EpsilonCollect::_last_used = 0; void EpsilonHeap::vmentry_collect(GCCause::Cause cause) { VM_EpsilonCollect vmop(cause); VMThread::execute(&vmop); }
, GC — , .
4.3。
, GC , , GC , . , allocate_work
, GC :
HeapWord* EpsilonHeap::allocate_or_collect_work(size_t size) { HeapWord* res = allocate_work(size); if (res == NULL && EpsilonSlidingGC) { vmentry_collect(GCCause::_allocation_failure); res = allocate_work(size); } return res; }
以上です!
5.
$ hg clone https://hg.openjdk.java.net/jdk/jdk/ jdk-jdk $ cd jdk-jdk $ curl https://shipilev.net/jvm/diy-gc/webrev/jdk-jdk-epsilon.changeset | patch -p1
OpenJDK :
$ ./configure --with-debug-level=fastdebug $ make images
:
$ build/linux-x86_64-server-fastdebug/images/jdk/bin/java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:+EpsilonSlidingGC -version openjdk version "13-internal" 2019-09-17 OpenJDK Runtime Environment (build 13-internal+0-adhoc.shade.jdk-jdk-epsilon) OpenJDK 64-Bit Server VM (build 13-internal+0-adhoc.shade.jdk-jdk-epsilon, mixed mode, sharing
6.
, GC ? :
- . . Hotspot , JVM fastdebug , GC.
- . , . , ( ) , .
- . , , , . - , .
, , :
$ CONF=linux-x86_64-server-fastdebug make images run-test TEST=gc/epsilon/ Building targets 'images run-test' in configuration 'linux-x86_64-server-fastdebug' Test selection 'gc/epsilon/', will run: * jtreg:test/hotspot/jtreg/gc/epsilon Running test 'jtreg:test/hotspot/jtreg/gc/epsilon' Passed: gc/epsilon/TestAlwaysPretouch.java Passed: gc/epsilon/TestAlignment.java Passed: gc/epsilon/TestElasticTLAB.java Passed: gc/epsilon/TestEpsilonEnabled.java Passed: gc/epsilon/TestHelloWorld.java Passed: gc/epsilon/TestLogTrace.java Passed: gc/epsilon/TestDieDefault.java Passed: gc/epsilon/TestDieWithOnError.java Passed: gc/epsilon/TestMemoryPools.java Passed: gc/epsilon/TestMaxTLAB.java Passed: gc/epsilon/TestPrintHeapSteps.java Passed: gc/epsilon/TestArraycopyCheckcast.java Passed: gc/epsilon/TestClasses.java Passed: gc/epsilon/TestUpdateCountersSteps.java Passed: gc/epsilon/TestDieWithHeapDump.java Passed: gc/epsilon/TestByteArrays.java Passed: gc/epsilon/TestManyThreads.java Passed: gc/epsilon/TestRefArrays.java Passed: gc/epsilon/TestObjects.java Passed: gc/epsilon/TestElasticTLABDecay.java Passed: gc/epsilon/TestSlidingGC.java Test results: passed: 21 TEST SUCCESS
? fastdebug . ? - .
7.
- spring-petclinic , Apache Bench GC! , , GC .
: -Xlog:gc -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:+EpsilonSlidingGC
:
:
Heap: 20480M reserved, 20480M (100.00%) committed, 19497M (95.20%) used GC(2) Step 0: Prologue 2.085ms GC(2) Step 1: Mark 51.005ms GC(2) Step 2: Calculate new locations 71.207ms GC(2) Step 3: Adjust pointers 49.671ms GC(2) Step 4: Move objects 22.839ms GC(2) Step 5: Epilogue 1.008ms GC(2) GC Stats: 70561 (8.63%) reachable from roots, 746676 (91.37%) reachable from heap, 91055 (11.14%) moved, 2237 (0.27%) markwords preserved GC(2) Heap: 20480M reserved, 20480M (100.00%) committed, 37056K (0.18%) used GC(2) Lisp2-style Mark-Compact (Allocation Failure) 20479M->36M(20480M) 197.940ms
200 ? GC! , . , , : ( — , ). - ( ).
, GC . , -Xlog:gc -XX:+UseSerialGC
— , , :
GC(46) Pause Young (Allocation Failure) 575M->39M(1943M) 2.603ms GC(47) Pause Young (Allocation Failure) 575M->39M(1943M) 2.606ms GC(48) Pause Young (Allocation Failure) 575M->39M(1943M) 2.747ms GC(49) Pause Young (Allocation Failure) 575M->39M(1943M) 2.578ms
, 2 . , , GC . -Xlog:gc -XX:+UseSerialGC
, , :
GC(3) Pause Full (Allocation Failure) 16385M->34M(18432M) 1969.694ms GC(4) Pause Full (Allocation Failure) 16385M->34M(18432M) 2261.405ms GC(5) Pause Full (Allocation Failure) 16385M->34M(18432M) 2327.577ms GC(6) Pause Full (Allocation Failure) 16385M->34M(18432M) 2328.976ms
, . .
8. ?
. , GC OpenJDK — , , .
:
. , // . . , , « » , , .
GC,
java.lang.ref.Reference.referent
— Java-, , , - .FinalReference
, .ReferenceProcessor
/ / .
VM. VM, , , . . , , , - , .
. — , GC, . , , , .
mark-compact GC Full GC fallbacks Shenandoah ( OpenJDK 8) G1 ( OpenJDK 10, JEP 307: «Parallel Full GC for G1» ).
-
. , , , . , «» — «» «» , .
- GC Handbook .
9.
? GC — , , , GC.
, - GC . , , GC (, Serial GC Parallel GC), .
広告の分。 , 5-6 2019, JPoint — Java-. — OpenJDK, GraalVM, Kotlin . .