読者です 読者をやめる 読者になる 読者になる

sugarlife's blog

Javaとか、http://twitter.com/sugarlife

Java 起動(Launcher)の仕組み

※:この記事は下書き中に本文ががっつり消えたため、知らずに部分的に端折ってるところがあるかもしれません。(´;ω;`)

Java、すなわち JVM (HotSpot) を立ち上げた時、どういった処理が行われているのでしょうか。正確に知りたい場合は OpenJDK のソースコードを読むのが最も確実ですが、概要レベルでどのような処理が行われていて、それがソースコードのどのあたりに書かれているのか案内があった方がすんなりと理解できます。と言うわけで、自分用のメモ書きをちょっとだけ整理してここで公開してみます。

なお、自分の理解をベースに記述しているので間違いが含まれている可能性があります。見つけた場合はそっとコメントか @sugarlife にお教え頂けると大変喜びます。

Java の動作概要について

Java、特に HotSpot の動作概要については、OpenJDK コミュニティによって「HotSpot Runtime Overview」という記事で解説されています。Java の起動時の処理についても「VM Lifecycle」という項目で説明されています。動作の概要を知りたい場合はこれらの記事を読むだけで理解できます。

和訳については有志らが取り組んでいる状況です。Java の動作に興味がある人は是非和訳をしてみませんか:)

この活動を見て、自分もこのメモ書きをちょっと手直しして公開してみようと思った次第です。

Java の起動 (Launcher)

HotSpot Runtime Overview で書かれている起動時における処理内容としては Launcher と JNI_CreateJavaVM が書かれています。Launcher というのは、ユーザが利用する java コマンド等のコマンド群 (java, javac, etc... ) 、およびそこから呼ばれるライブラリ (JLI、libjli) 部分を指します。「Launcher」とだけ書かれていた場合は、java コマンド等のコマンド群を指すことが多いです。
JNI_CreateJavaVM は The invocation API と呼ばれる API の一つで、JVM(HotSpot) そのものをロード & 初期化するメソッドです。現在の実装では一つのプロセスに複数VM は作成不可能なため、一度のみ実行されます。

ここでは主に Launcher 部分について触れて行きます。なお、ソースコードへのリンクは jdk8 の tip(最新コミット) にリンクしています。jdk8 の最新版ではないことに注意してください。最新版は jdk8 の部分を jdk8u に変えることで確認できます。

  • java コマンドを叩く
  • main() / WinMain() [jdk/src/share/bin/main.c]
  • JLI_Launch() [jdk/src/share/bin/java.c]
    • LoadJavaVM() より libjvm をロード。Linux の場合は libjvm.so ファイルをダイナミックロード。
    • HotSpot Runtime Overview に記載されている Launcher の 1. ~ 4. 部分が実行される。
    1. コマンドライン引数の解析。適切な VM を起動するために -client とか -server はランチャ内で利用され、それ以外の引数は JavaVMInitArgs を通じて VM に渡される。
    2. ヒープサイズやコンパイラ種別(client or server)が設定されていない場合、適当な値を設定する。
    3. LD_LIBRARY_PATHやCLASSPATH等の環境変数を設定する。
    4. メインクラスが指定されて居ない場合、jar ファイルのマニフェストファイルからメインクラスを確認する。
  • ContinueInNewThread() [jdk/src/share/bin/java.c]
  • ContinueInNewThread0() [OSによって異なる]
    • non primordial thread を作成、実行する。non primordial thread のエントリポイントは JavaMain()
  • JavaMain() [jdk/src/share/bin/java.c]
    • HotSpot Runtime Overview に記載されている 5. 以降の処理が行われる。
    1. (ContinueInNewThread0() で non primordial thread を作成し、)JNI_CreateJavaVM()を呼んで VM を作成・初期化する。新しいスレッド(non primordial thread)を作成しているのは、この処理自体を行っているスレッド(primordial thread)が、環境によっては色々な制限があり、VMの作成上で制約(Windows上でのスタックサイズなどによる制約など)があるため。
    2. メインクラスを読み込んで、メインクラスからメインメソッドの属性を取得する。
      • LoadMainClass() 以降の処理。メインメソッドを取得しているのは GetStaticMethodID()。
    3. CallStaticVoidMethod() とコマンドラインに渡された引数を使用してメインメソッドを呼び出す。
    4. メインメソッドが完了したら、待機中の例外 (pending exception) がないか確認し、exit status が渡されてないことを確認する。例外は ExceptionOccurred() でクリアされる。メソッドが成功していれば戻り値は 0 に、そうでなければ呼び出し元のプロセスに返却される。
    5. メインスレッドは DetachCurrentThread() によりデタッチされる。これによりスレッドカウントをデクリメントし、DestroyJavaVM() を安全に呼び出し、且つこのスレッドが VM 内の操作やスタック上に Java フレームが存在しないことを保証できる。
      • JavaMain()は最終的に LEAVE() マクロを呼び出し、DetachCurrentThread()、DestroyJavaVM()を呼んでいる。

以上、Launcher 編でした。

JNI_CreateJavaVM() について。

ここは真面目にやると深いので、

まとめ

OpenJDKこわい。

JJUG CCC 2014 Fall で Concurrent Mark & Sweep Garbage Collection について話します。良かったらマサカリを置いて来てね!