前提条件
最近、システム業務の比較的大量のために、GCログの生成から、いくつかのシステムのGCチューニングを実施する必要があります。しかし、過去に特にこの作業を行ったわけではなく、散々蓄積されてきたことを踏まえ、ここでは比較的包括的にまとめてみたいと思います。この記事は、HotSpot VMは、Oracle Hotspot VMまたはOpenJDK Hotspot VM、バージョンJDK8のみです。
GCとは
ガベージコレクションは「ゴミ収集」と訳され、主観的にはゴミを見つけて捨てることだと考えられています。JVMでは、GCは逆の方法で実装されています。GCの目的は、使用中のすべてのオブジェクトを追跡し、残りのオブジェクトをゴミとしてマークすることです。ゴミとしてマークされたオブジェクトは、これらのゴミオブジェクトによって占有されたメモリを取り戻すためにパージされ、その結果、自動メモリ管理が可能になります。
セグメンテーション仮説
弱分裂仮説 | 被験者の多くは若くして死亡 |
強発散仮説 | 古いものほど死ににくい |
弱い離散化仮説は、さまざまな種類のプログラミング・パラダイムやプログラミング言語で確認されていますが、強い離散化仮説は、現在のところ十分な証拠がなく、その視点はまだ議論されています。
世代ゴミコレクタの主な設計目的は、空間スループットを増加させながらリサイクルプロセスのダウンタイムを短縮することです。若い世代のオブジェクトをリサイクルするために複製アルゴリズムが使用される場合、予想されるダウンタイムは、二次リサイクルの後に生き残るオブジェクトの総数に大きく依存します。
若い世代の全体のスペースが小さすぎる場合、再要求プロセスは比較的高速ですが、2つの再要求の間の短い間隔のために、若い世代のオブジェクトが「死に達する」のに十分な時間がない可能性があり、その結果、再要求されたメモリがあまりない、次のような状況につながる可能性があります:
- 若い世代は頻繁にリサイクルされ、生き残るためにコピーされる必要があるオブジェクトの数は、スレッドをストールし、データのためにスタックをスキャンするごみコレクターのオーバーヘッドを増加させます。
- 若い世代のモノを古い世代に多く普及させると、古い世代がすぐに埋まってしまい、ヒープ全体のゴミ回収率に影響します。
- 新しい世代のオブジェクトへの変更は古い世代のオブジェクトへの変更よりも頻繁に行われ、若い世代のオブジェクトが早期に古い世代に昇格した場合、多数の更新操作がフューザーの書き込みバリアに比較的大きな負担をかける可能性があることを示す多くの証拠があります。
- オブジェクトの昇格により、プログラムの作業セットは疎になります。
世代別ゴミ収集機の設計者は、上記の側面をバランスさせる技術を持っています:
- 二次回収率を最大化すること。
- 二次回収のコストを最小限に抑えること。
- リサイクルにコストがかかる主なリサイクルを減らすため。
- 定着器のメモリ管理オーバーヘッドを適切に削減するため。
弱分割世代仮説に基づき、JVMではヒープメモリは若い世代と古い世代に分けられ、古い世代はテニュアと呼ばれることもあります。
JVM は世代ごとに異なるゴミ収集アルゴリズムを提供します。実際、異なる世代のオブジェクトは互いに参照し合うことがあり、これらの参照されるオブジェクトも世代ゴミ収集のGCルートとして考慮されます。弱い世代仮説は、特定のシナリオで特定のアプリケーションに適用できない場合があります。GCアルゴリズムは、若いオブジェクトまたは古いオブジェクトのために最適化されており、JVMのゴミ収集性能は、「中程度」の寿命を持つオブジェクトの性能よりも相対的に劣っています。寿命が "中程度 "のオブジェクトの場合。
オブジェクト判断のためのアルゴリズム Alive
JVMの到達可能性アルゴリズムによって、オブジェクトがGCルートに参照チェーンで接続されていない場合、そのオブジェクトは到達不可能であることを意味します。
// psTasks.hpp
class ScavengeRootsTask : public GCTask {
public:
enum RootType {
universe = 1,
jni_handles = 2,
threads = 3,
object_synchronizer = 4,
flat_profiler = 5,
system_dictionary = 6,
class_loader_data = 7,
management = 8,
jvmti = 9,
code_cache = 10
};
private:
RootType _root_type;
public:
ScavengeRootsTask(RootType value) : _root_type(value) {}
char* name() { return (char *)"scavenge-roots-task"; }
virtual void do_it(GCTaskManager* manager, uint which);
};
// psTasks.cpp
void ScavengeRootsTask::do_it(GCTaskManager* manager, uint which) {
assert(ParallelScavengeHeap::heap()->is_gc_active(), "called outside gc");
PSPromotionManager* pm = PSPromotionManager::gc_thread_promotion_manager(which);
PSScavengeRootsClosure roots_closure(pm);
PSPromoteRootsClosure roots_to_old_closure(pm);
switch (_root_type) {
case universe:
Universe::oops_do(&roots_closure);
break;
case jni_handles:
JNIHandles::oops_do(&roots_closure);
break;
case threads:
{
ResourceMark rm;
Threads::oops_do(&roots_closure, NULL);
}
break;
case object_synchronizer:
ObjectSynchronizer::oops_do(&roots_closure);
break;
case flat_profiler:
FlatProfiler::oops_do(&roots_closure);
break;
case system_dictionary:
SystemDictionary::oops_do(&roots_closure);
break;
case class_loader_data:
{
PSScavengeKlassClosure klass_closure(pm);
ClassLoaderDataGraph::oops_do(&roots_closure, &klass_closure, false);
}
break;
case management:
Management::oops_do(&roots_closure);
break;
case jvmti:
JvmtiExport::oops_do(&roots_closure);
break;
case code_cache:
{
MarkingCodeBlobClosure each_scavengable_code_blob(&roots_to_old_closure, CodeBlobToOopClosure::FixRelocations);
CodeCache::scavenge_root_nmethods_do(&each_scavengable_code_blob);
AOTLoader::oops_do(&roots_closure);
}
break;
default:
fatal("Unknown root type");
}
// Do the real work
pm->drain_stacks(false);
}
- Universe::ops_do:VMの静的データ構造の一部で、GCヒープ内のオブジェクトへのアクティブ参照など。
- Threads::oops_do: すべてのスレッドの仮想マシンスタック、具体的には、Javaスレッドが現在アクティブになっているスタックフレーム内のGCヒープ内のオブジェクトへのすべての参照、言い換えれば、現在呼び出されているすべてのメソッドのすべての参照型引数、ローカル変数、または一時的な値である必要があります。
global handle
オブジェクト・シンクロナイズドによって関連付けられているすべてのオブジェクトは、ソース・コードから見ると、ObjectMonitorでBlock状態にあるオブジェクトであり、Javaコード・レベルから見ると、synchronizedキーワードによってロックされているか、ロックされるのを待っているオブジェクトであるはずです。ObjectSynchronizer::oops_do
すべてのスレッドで ThreadProfiler。FlatProfiler::oops_do
システム・ディクショナリは、システム・ディクショナリとも呼ばれ、Klassを指すレコードで、KEYはKalssNameとクラスローダで構成されるエントリです。システム辞書にはすべてのクラスローダーが含まれています。SystemDictionary::oops_do
すべてのロードされたクラスまたはロードされたシステム・クラス。- Management::oops_do: MBeanが保持するオブジェクト。
ClassLoaderDataGraph::oops_do
JVMTI によってエクスポートされたオブジェクト、ブレークポイント、またはオブジェクト割り当てイベントコレクターに関連するオブジェクト。JvmtiExport::oops_do
コードキャッシュ。- AOTLoader::oops_do: AOT ローダー関連で、AOT 関連のコードキャッシュを含みます。
他にも考えられる参考文献はあります:
CodeCache::scavenge_root_nmethods_do
常駐文字列
JVMのメモリプールは
JVMはメモリプールを複数の領域に分割し、以下に各領域の構成と基本的な機能を説明します。これは、GCアルゴリズムが以下に紹介されるとき、異なるメモリプール空間でゴミ収集がどのような役割を果たすかを理解するのに便利です。
- オールド・ジェネレーション:一般にテニュアと呼ばれる世代。
Eden
聖書、旧約聖書、創世記によれば、エデンの園は地上の楽園であり、そこで神、エホバは人類の始祖アダムをご自分に似せて創造し、アダムの肋骨の一本から女性イブを創造し、最初の男女をエデンの園に住まわせました。
エデン(Eden)は、オブジェクト生成時にオブジェクトを割り当てるための通常のメモリ領域です。Edenはさらに、Eden空間に常駐する1つ以上のThread Local Allocation Bufferに分割され、TLABはスレッド排他です。JVMは、スレッドがほとんどのオブジェクトを作成時に対応するTLABに直接割り当てることができ、複数のスレッド間の同期のパフォーマンスオーバーヘッドを回避できます。
TLABでオブジェクトの割り当てを行うことができない場合、オブジェクトの割り当て操作はエデン内の共有スペースで行われます。エデン全体に十分な空き領域がない場合、エデン内の空き領域を増やすためにYGC が起動されます。YGCがトリガーされた後、まだ十分なメモリがない場合、オブジェクトは古い年代に割り当てられます。
このフェーズはマーキングフェーズと呼ばれます。
つまり、若い世代のオブジェクトが古い世代のオブジェクトに保持されている可能性があります。このとき、古い世代のオブジェクトを走査しなければ、古い世代のオブジェクトに保持されている若い世代のオブジェクトが、到達可能性アルゴリズムによって到達可能かどうかを分析することができません。JVMはこの問題を解決するために、カード・マーキングのアプローチを採用しています。JVMはCard Markingを使うことでこの問題を解決しているので、ここではCard Markingの実装の詳細については説明しません。
マーキングフェーズが完了すると、エデン内のすべての生存オブジェクトは生存スペースの 1つにコピーされます。複製フェーズが完了すると、Eden全体は空であるとみなされ、追加のオブジェクトのために再割り当てすることができます。ここで使用されるGCアルゴリズムはマークコピーアルゴリズムと 呼ばれます: 生き残ったオブジェクトをマークし、生存者空間の 1つにコピーします。
エデンがこれだけ導入されているのは、TLABとカードマーキングがJVMの相対的な底辺に実装されているためで、おそらく知ることができます。
Survivor Spaces
サバイバースペースはサバイバースペースとも呼ばれ、サバイバースペースの最も一般的な名称はfromとtoです。 サバイバースペースで最も重要なことは、2つのエリアのうち1つは常に空であるということです。
生存者領域の空き領域には、次のYGCがトリガーされた後にのみオブジェクトが配置されます。若い世代の生存オブジェクトはすべて生存先領域にコピーされ、この処理が完了すると生存先領域にはアクティブなオブジェクトが保持され、生存元領域は空になります。つまり、次のYGCが発生した後に生き残っているオブジェクトはfrom survivor領域にコピーされ、to survivor領域は空になります。
2つの生存者空間の間で上記のような生存オブジェクトの複製プロセスが繰り返された後、生存オブジェクトの中には老年期に昇格するのに十分な「年齢」を持つオブジェクトが存在し、これらのオブジェクトは生存者空間から老年期空間に移動し、その後、連絡が取れなくなるまで老年期に居住することになります。
オブジェクトがエデンで生まれ、最初のYGCを生き残り、サバイバー・スペースに収容できる場合、オブジェクトはサバイバー・スペースにコピーされ、その年齢は1に設定されます。オブジェクトがサバイバー・スペースで生き残る各YGCごとに、オブジェクトの年齢は1ずつ増加し、その年齢が老齢への昇格の年齢閾値まで増加すると、老齢への昇格、すなわち老齢への移動が行われます。オブジェクトの年齢は1ずつ増加し、年齢が古い年齢への昇格のための年齢しきい値に達すると、古い年齢に昇格します。-XX:MaxTenuringThreshold=n
古い年齢への昇格のための年齢しきい値のJVMパラメータは:
-XX:MaxTenuringThreshold=n | サバイバー・スペースの年齢閾値 高年齢化を促進するための存続オブジェクト | 1<= n <= 15 | 15 |
-XX:MaxTenuringThreshold
注目すべき点は、JVMはデフォルト値をオプションの最大値である15に設定することです。
MaxTenuringThreshold
JVMはまた、動的なオブジェクトの年齢判定機能を持っています、JVMは常に生存オブジェクトの年齢が古い年齢に昇格するために到達しなければならない必要はありません、生存空間の同じ年齢のすべてのオブジェクトのサイズの合計が生存空間の半分よりも大きい場合、オブジェクトの年齢は、古い年齢の年齢以上であるか、または等しい直接古い年齢に昇格することができます。になるのを待つ必要はありません:
ObjectType-1 | 60% | 5 | 次のページ YGCは老齢期に直接昇格した場合 |
ObjectType-2 | 1% | 6 | 次のページ YGCは老齢期に直接昇格した場合 |
ObjectType-3 | 10% | 4 | 生存しているオブジェクトの年齢が1歳上昇した場合、次のYGC |
ある物体が老年期を迎えるいくつかのシナリオを簡単にまとめることができます:
MaxTenuringThreshold
複数のYGCオブジェクトが生き残り、オブジェクトの昇格を引き起こす設定年齢に達します。- なぜなら、ダイナミックなオブジェクトの年齢判定は、オブジェクトの昇格につながるからです。
- ラージ・オブジェクトは直接旧世代に入ります。ラージ・オブジェクトは通常、連続した大量のメモリを必要とするJavaオブジェクトを指し、最も一般的なのは大きな配列オブジェクトや非常に長い文字列です。
- 若い世代にスペースがなくなると、古い世代がスペース割り当ての保証を行い、その場合、オブジェクトも古い世代に直接割り当てられます。
Tenured
旧世代(一般的にはTenuredと呼ばれます)は、一般的にメモリ空間の実装がより複雑です。古い世代は一般に若い世代よりもはるかに大きく、そこに含まれるオブジェクトは一般に「メモリジャンク」ではないため、古い世代のオブジェクトのリサイクル率は一般に低くなります。
古い世代は、一般的に若い世代よりもGCを経験する頻度が低く、古い世代のオブジェクトのほとんどは生き残ると予想されるため、マークアンドコピーアルゴリズムは古い世代には適用されません。古い世代のGCアルゴリズムは、一般的に以下の一般的なルールで、メモリの断片化を最小限にするようにオブジェクトを移動します:
- 到達可能なすべてのオブジェクトを走査し、GC Roots でマークします。
- GC Root に対して到達不可能なすべてのオブジェクトを削除します。
- 生き残ったオブジェクトを古いメモリ空間の先頭に順次コピーすることで、古いメモリ空間の内容を圧縮するプロセスは、主にメモリの過度な断片化を避けるための明示的なメモリ圧縮で構成されています。
Metaspace
JDK8のJVMのメモリプールではまた、永久世代(永久世代)と呼ばれる空間を定義し、メモリ空間のこの部分は、主にクラス情報などのメタデータを格納するために使用され、それはまた、常駐文字列などの他のデータコンテンツを格納します。MaxTenuringThreshold=15
実際、パーマネント・ジェネレーションはJava開発者に多くの問題を引き起こしています。ほとんどの場合、パーマネント・ジェネレーションを設定する必要がある領域を予測するのは困難であるため、開発者がメタデータや文字列定数プールの正確なサイズを予測することも難しく、いったんメタデータやその他のコンテンツの割り当てに失敗すると、例外に遭遇することになるからです。メモリのオーバーフローによる例外を除いて、問題が正確に特定できる前提で例外が発生した場合、唯一の解決策はVMパラメータを介して永続的な生成のメモリを増やすことですが、これも治療法ではなく対症療法です。
メタデータやその他のコンテンツは予測不可能であるため、JDK [8 +]は永久的な生成を削除し、新しいメモリ領域メタスペース(metaspace)は、他の多くの雑多なアイテムは、Javaヒープに移動されます。クラス定義情報やその他のメタデータは、現在メタスペースに直接ロードされます。メタスペースは、Javaオブジェクトをホストするヒープ・メモリーから分離された、マシンのローカル・メモリーに割り当てられたメモリー領域です。-XX:MaxTenuringThreshold=n
デフォルトでは、メタ空間のサイズは、Javaプログラムに割り当てられるマシン・ローカル・メモリの最大量によってのみ制限され、新しいクラスの追加によって例外が発生するシナリオを基本的に回避します。
共通メモリプール関連 VM パラメータ
- -Xmx そして-Xms
-Xmx | 最大ヒープ・メモリー・サイズの設定 | VMバージョンにより、下限制御が可能 | - |
-Xms | 最小ヒープ・メモリー・サイズの設定 | VMバージョンにより、下限制御が可能 | - |
- -Xmn,,-XX:NewRatio そして-XX:SurvivorRatio
-Xmn | 若い世代のメモリサイズの設定 | - | - |
-XX:NewRatio= | 旧世代のメモリ・サイズと若い世代のメモリ・サイズの比率を設定します。これを4に設定すると、若い世代がヒープ・メモリの1/5を占有することになります。 | - | 4 |
-XX:生存率=。 | from:to:Eden=1:1:8で、Eden領域とsurvivor領域のメモリサイズの比率を8に設定。 | - | 8 |
GC
- パーシャルGC:つまり、GCヒープ全体を収集しないパーシャルGCです。
Young GC
収集のみyoung gen
GCはconcurrent collection
Old GC:旧世代のGCのみを収集、現在はCMSのみがこのモード。- 混合GC:若い世代のGC全体と古い世代のGCの一部を収集します。
- フルGC:若い世代、古い世代、パーマ世代、およびパターンの他のすべての部分を含む、ヒープ全体を収集します。
HotSpot VMは何年もかけて進化してきたため、GCの用語は部外者が解読するのに混乱するようになり、Minor GC、Major GC、Full GCと呼ばれるようになりました。
Minor GC
マイナーGCはマイナー・ガベージ・コレクションとも呼ばれ、直訳すると二次的なゴミ収集となり、その定義は比較的明確です。マイナー・ガベージ・コレクションの処理が発生します:
- MinorGCは、JVMが新しいオブジェクトのためのメモリ空間を確保できないときに常にトリガされます。これは、Edenのメモリが一杯になったときなどの一般的なシナリオで、オブジェクトの割り当ての発生率が高いほど、MinorGCの発生頻度も高くなります。
- 古い世代のオブジェクトはマイナー GC の間無視されます。古い世代のオブジェクトによって参照される若い世代のオブジェクトは、GC Roots の一部とみなされ、若い世代のオブジェクトによって参照される古い世代のオブジェクトは、タグ付けフェーズの間、単に無視されます。
- マイナーGCはStop The Worldを引き起こし、アプリケーションスレッドの一時停止として現れます。ほとんどの場合、エデン内のオブジェクトのほとんどがゴミとみなすことができ、ゴミがサバイバー空間にコピーされない場合、Minor GCの一時停止時間は非常に短いか、無視できる程度になります。一方、サバイバー空間にコピーされる必要のある多数のオブジェクトがEdenに存在する場合、Minor GCの一時停止時間は大幅に増加します。
Major GCとフルGC
メジャーGCとフルGCは、現在のところ正式には定義されていない2つの用語です。具体的には、メジャーGCやフルGCは、JVMの仕様やゴミ収集の研究論文では定義されていませんが、言い伝えや慣例によると、以下のような違いがあります:
- メジャーGC:旧世代のガベージコレクション。
- フルGC:若い世代も古い世代も含めたヒープ全体のガベージコレクション。
実際、GCプロセスは非常に複雑で、多くのMajor GCはMinor GCによって引き起こされるため、Major GCかMinor GCかを厳密に分けることはほとんど不可能です。一方、最近のG1収集アルゴリズムのようなゴミ収集アルゴリズムは、部分的なゴミ収集を提供しており、どの領域が収集されるかによって純粋にGCの種類を分類することは不可能であることを示しています。
上記のいくつかの理論や情報源は、GC が Major GC か Minor GC かを議論したり心配したりする代わりに、GC プロセスがアプリケーションスレッドをストールさせるかどうか、または GC プロセスがアプリケーションスレッドと同時に実行できるかどうかにもっと労力を費やすべきであることを示しています。
よく使われるGCアルゴリズム
G1 アルゴリズムは比較的複雑で、当分の間ここで分析することはできません。
GCアルゴリズムの目的
GCアルゴリズムには2つの主な目的があります:
- 生存しているすべてのオブジェクトを見つけ、それらにタグを付けます。
- すべての無駄なオブジェクトを削除します。
生存しているオブジェクトを見つけることは、主にGC Rootsの到達可能性アルゴリズムに基づいていますが、ラベリング段階についてはいくつかの注意点があります:
- マーキングフェーズの間、すべてのアプリケーションスレッドは一時停止され、アプリケーションスレッドはリストアポイントに情報を保存するために一時停止されます。
- マーキングフェーズの継続時間は、ヒープ内のオブジェクトの総数やヒープのサイズには依存せず、生存しているオブジェクトの総数に依存するため、ヒープのサイズを大きくしてもマーキングフェーズの継続時間に大きな影響はありません。
ラベリング段階が完了した次の段階は、すべての無駄なオブジェクトを削除することで、その処理方法によって3つの一般的なアルゴリズムに分けられます:
- マーク・アンド・スウィープ、マーク・スウィープとも呼ばれる掃除 。
- コンパクト - マーク・スウィープ・コンパクト、マーク・クリーン・コンパクトとも呼ばれる圧縮。
- コピー -- コピー、マーク・アンド・コピー、マーク・コピーとも。
Mark-Sweep
Mark-Sweepアルゴリズムは、Mark-Cleanアルゴリズムとも呼ばれ、間接的なリサイクルアルゴリズムで、ゴミ対象物そのものを直接検出するのではなく、まず生存しているすべての対象物を特定し、次に他の対象物がゴミ対象物であると判断します。これは最もシンプルで基本的な回収アルゴリズムで、主にマーキングとクリーニングの2段階から構成されています:
- 最初のフェーズはトレースフェーズです。コレクタはGC Rootsから始まるすべての到達可能なオブジェクトをトラバースし、生き残ったものをマークします。
- 2番目のフェーズはクリーンアップフェーズです。コレクターは、すべてのマークされていないオブジェクトをクリーンアップしてリサイクルします。
Mark-Sweep-Compact
メモリの断片化は、非モバイル・コレクション・アルゴリズムが解決できない問題の1つです。ヒープに領域があるにもかかわらず、メモリ・マネージャは、より大きなオブジェクトの割り当てニーズを満たす連続したメモリ・ブロックを見つけることができないか、適切な空きメモリ領域を見つけるのに長い時間がかかります。
Mark-Sweep-Compactアルゴリズム、またはMark-Clean-Compactアルゴリズムも、3つの主要なステージからなる間接リサイクルアルゴリズムです:
- マーキングフェーズ:コレクターはGC Rootsから始まるすべての到達可能なオブジェクトを走査し、生き残ったものをマークします。
- クリーンアップフェーズ:コレクターは、すべてのタグ付けされていないオブジェクトをクリーンアップしてリサイクルします。
- 圧縮フェーズ:コレクターは、生存しているすべてのオブジェクトをヒープ・メモリの先頭に移動させ、終了境界を超えたメモリ空間をクリアします。
ヒープ・メモリをコンパクトに整理することで、外部メモリの断片化の問題を効果的に減らすことができ、これはmark-clean-compressアルゴリズムの利点です。
Mark-Copy
マーク・コピー・アルゴリズム(Mark-Copyalgorithm)は、マーク・クリーン・圧縮アルゴリズム(Mark-Clean-Compress algorithm)と非常によく似ていますが、マーク・コピー・アルゴリズム(Mark-Copyalgorithm)は、マーキングとクリーニングが完了した後に、すべての生存オブジェクトをメモリの別の領域(生存空間)にコピーするという重要な違いがあります。これは主に3つのフェーズで構成されます:
- マーキングフェーズ:コレクターはGC Rootsから始まるすべての到達可能なオブジェクトを走査し、生き残ったものをマークします。
- クリーンアップフェーズ:コレクタは、すべてのタグ付けされていないオブジェクトをクリーンアップしてリサイクル します。
- レプリケーションフェーズ:生存しているすべてのオブジェクトをSurvivor Spacesの特定のスペースにコピーします。
マークアンドコピー・アルゴリズムは、メモリ断片化の問題を回避しますが、ハーフ・リージョン・コピーの再生成が使用され、リージョンが使用可能なメモリが元の半分になるため、より高価になります。
まとめ
JVMとGCは、Java開発者は、実際にはまだかなり多く含まれている知識の内容をマスターする必要がありますが、この記事はまた、いくつかの基本的な概念への簡単な紹介です:
- セグメンテーション仮説。
Minor GC
,,Major GC
そしてFull GC
- JVMJVMにおけるメモリプールの構成。
- よく使われるGCアルゴリズム
後で、GCコレクタのコロケーションとGCログの表示、JVMが提供するツールなどを分析します。
- Java仮想マシン入門-第3回
- ガベージコレクションハンドブック
- OpenJDK HotSpot VM部分ソースコード