HotspotVMとは何かという点から話をはじめましょう。
HotspotVMはOracle社主導で開発されているもっともポピュラーなJavaVMです。
HotspotVMの特徴は「プログラムの実行頻度の高い部分のみ機械語にコンパイルする」という点です。これにはプログラムの実行時間を多く費やす部分(実行頻度の高い部分)を最適化し、プログラム全体の実行時間を短くしようという狙いがあります。また、機械語へのコンパイルをある程度の範囲に絞るため、コンパイル時間が短くなるという効果もあります。「実行頻度の高い部分」をHotspotと呼びます。この点がHotspotVMの名前の由来となっています。
HotspotVMのもう1つの特徴は複数のGCアルゴリズムが実装されているという点でしょう。通常GCアルゴリズムは、レスポンス性能とスループット性能のどちらかを優先してチューンアップします。一般的にレスポンス性能を優先したGCアルゴリズムはスループット性能が低下します。逆にスループット性能を優先したGCアルゴリズムはレスポンス性能が低下します。そのた、さまざまな要素から現在のところは完璧なGCというものが存在していません。HotspotVMはこのジレンマに対する回答として複数のGCアルゴリズムを実装するという手法をとりました。プログラマはアプリケーションの特性に合わせてHotspotVMのGCアルゴリズムを選択できます。つまり、レスポンス性能を求めるアプリケーションの場合は、それに適したGCアルゴリズムを「プログラマ」が選択できるということです。プログラマによるGCアルゴリズムの選択は優れた手法だと言えるでしょう。
Javaの開発用プログラミングツール群のことをまとめて「Java SE Development Kit(JDK)」と呼びます。
JDKにはHotspotVMの他に、JavaのソースコードをJavaバイトコードにコンパイルするJavaコンパイラ、Javaソースコードからドキュメントを生成するツールなどが同梱されています。
2006年11月、当時のSunはJDKのソースコードをGPLv2*1の下で無償公開することを発表しました。このオープンソース版のJDKをOpenJDKと呼びます。
OpenJDKの最新バージョンはOpenJDK7と呼ばれています。一方、オラクルが正式に提供するJDKの最新バージョンはJDK7と呼ばれています。OpenJDK7とJDK7は名前は異なるものですが、両者のコードはほぼ同じです。ただし、JDKにはライセンス的にクローズドなコードが一部あるため、OpenJDKではそれをオープンソースとして書き直しているようです。
OpenJDKの公式サイトは次のURLです。
図1.1: OpenJDK公式サイト
現在の最新のリリース版はOpenJDK7です。開発マイルストーンは次のURLで現在でも閲覧可能です。
http://openjdk.java.net/projects/jdk7/milestones/
本章ではOpenJDK7の執筆時の最新バージョンである「jdk7-b147」を解説対象とします。
では、ソースコードをダウンロードしましょう。
OpenJDK7の最新開発バージョンのソースコードは次のURLからダウンロードできます。
http://download.java.net/openjdk/jdk7/
特定の開発バージョンのソースコードを入手したい場合は「Mercurial」*2を使います。MercurialとはPythonで作られたフリーの分散バージョン管理システムです。
OpenJDKは複数のプロジェクトを持っており、プロジェクト分のリポジトリが存在します。今回はMercurialを使ってHotspotVMプロジェクトのリポジトリのみからソースコードを入手しましょう。次のコマンドを入力すると、リポジトリからコードツリーをチェックアウトできます。
hg clone -r jdk7-b147 http://hg.openjdk.java.net/jdk7/jdk7/hotspot hotspot
HotspotVMのソースコード内の「src」という名前のディレクトリにHotspotVMのソースコードが置かれています。
表1.1: ディレクトリ構成
ディレクトリ | 概要 |
---|---|
cpu | CPU依存コード群 |
os | OS依存コード群 |
os_cpu | OS、CPU依存コード群(Linuxでかつx86など) |
share | 共通コード群 |
表1.1内最後の「share」ディレクトリ内には「vm」というディレクトリがあります。この「vm」ディレクトリの中にHotspotVMの大半部分のコードが置かれています。
表1.2: vm内ディレクトリ構成
ディレクトリ | 概要 |
---|---|
c1 | C1コンパイラ |
classfile | Javaクラスファイル定義 |
gc_implementation | GCの実装部 |
gc_interface | GCのインターフェイス部 |
interpreter | Javaインタプリタ |
oops | オブジェクト構造定義 |
runtime | VM実行時に必要なライブラリ |
また、「src」ディレクトリ内のソースコード分布を表1.3に示します。
表1.3: ソースコード分布
言語 | ソースコード行数 | 割合 |
---|---|---|
C++ | 420,791 | 93% |
Java | 21,231 | 5% |
C | 7,432 | 2% |
HotspotVMは約45万行のソースコードから成り立っており、そのほとんどがC++で書かれています。
HotspotVM内のほとんどのクラスは次の2つのクラスのいずれかを継承しています。
ソースコード上ではこれらのクラスが頻出しますので、ここで意味をおさえておきましょう。
CHeapObjクラスはCのヒープ領域上のメモリで管理されるクラスです。CHeapObjクラスを継承するクラスのインスタンスは、Cヒープ上にメモリ確保されます。
CHeapObjクラスの特殊な点はオペレータのnewとdeleteを書き換え、C++の通常のアロケーションにデバッグ処理を追加してるところです。このデバッグ処理は開発時にのみ使用されます。
CHeapObjクラスにはいくつかのデバッグ処理が実装されていますが、今回はその中のメモリ破壊検知機能を見てみましょう。
デバッグ時、CHeapObjクラス(または継承先クラス)のインスタンスを生成する際に、わざと余分にアロケーションします。アロケーションされたメモリのイメージを図1.2に示します。
図1.2: デバッグ時のCHeapObjインスタンス
余分にアロケーションしたメモリ領域を図1.2に示すとおり「メモリ破壊検知領域」として使います。メモリ破壊検知領域には0xABという値を書き込んでおきます。CHeapObjクラスのインスタンスとして図1.2の真ん中に示したメモリ領域を使用します。
そして、CHeapObjクラスのインスタンスのdelete時にメモリ破壊検知領域が0xABのままであるかをチェックします。もし、書き換わっていれば、CHeapObjクラスのインスタンスの範囲を超えたメモリ領域に書き込みがおこなわれたということです。これはメモリ破壊がおこなわれた証拠となりますので、発見次第エラーを出力し、終了します。
AllStaticクラスは「静的な情報のみをもつクラス」という意味を持つ特殊なクラスです。
AllStaticクラスを継承したクラスはインスタンスを生成なくなります。そのため、グローバル変数や関数を1つの名前空間にまとめたいときに、AllStaticクラスを継承します。継承するクラスにはグローバル変数やそのアクセサ、静的(static)なメンバ関数など、クラスから使える情報のみが定義されます。
HotspotVMはさまざまなOS上で動作する必要があります。そのため、各OSのAPIを統一のインタフェースを使って扱う便利な機構が用意されています。
share/vm/runtime/os.hpp
80: class os: AllStatic { ... 223: static char* reserve_memory(size_t bytes, char* addr = 0, 224: size_t alignment_hint = 0); ... 732: };
osクラスはAllStaticクラスを継承するためインスタンスを作らずに利用します。
osクラスに定義されたメンバ関数の実体は各OSに対して用意されています。
上記ファイルはOpenJDKのビルド時に各OSに合う適切なものが選択され、コンパイル・リンクされます。os/posix/vm/os_posix.cppはPOSIX API準拠のOS(LinuxとSorarisの両方)に対してリンクされます。例えばLinux環境ではos/posix/vm/os_posix.cppとos/linux/vm/os_linux.cppがリンクされます。
そのため、例えば上記のshare/vm/runtime/os.hppで定義されているos::reserve_memory()を呼び出し時には、各OSで別々のos::reserve_memoryが実行されます。
os:xxx()というメンバ関数はソースコード上によく登場しますので、よく覚えておいてください。
御意見・御感想・誤植の指摘などは@nari3もしくはauthorNari/g1gc-impl-book - GitHubまでお願いします。
(C) 2011-2012 Narihiro Nakamura