はじめに
私はかれこれ4年近く Kotlin を触ってきました。
しかし、ふとした時に JVM で書いたプログラムが実行される仕組みについて、他の人にうまく説明できないことに気づきました。
そのため、この記事では自分の言語化と、知識の再取得を兼ねてJVMの仕組みについてまとめます。
コンパイル
Javaのコードはコンパイルすることによって、 .class ファイルに変換されます。これはバイトコードと呼ばれ、JVMが解釈して実行するために作られた中間表現です。
実行の流れ
- JVM起動
- ロード(クラスローダーが必要な
.classファイルを読み込む) - リンク(クラスが破損していないかの検証や参照の解決)
- 初期化(static フィールドの初期化など)
- main メソッド実行
覚えておきたいこと
- バイトコードは1行ずつ実行される
- つまり、インタプリタ実行
- これを改善するために「JITコンパイラ」というのがある
- 実行時にコンパイルを行い、処理を最適化する
- Javaは
javacによるクラスファイルへのコンパイルと、この実行時のコンパイルがある
- JVMはスタックマシン
- 「スタック」と呼ばれる領域に命令を Push / Pop しながらプログラムを実行していく
JVM には種類がある
- JVM の仕様は Java 仮想マシン仕様によって規定されているが、抽象的
-
.classファイルを読み込んで正しく実行できればOK - あとはJVMの実装者次第
-
- 各種ベンダが独自 JVM を提供している
- e.g.
- Oracle HotSpot VM -> HotSpot という JIT コンパイラを採用
- Oracle Graal VM -> Graal という JIT コンパイラを採用。AOT コンパイルでのネイティブイメージ化もされている
- e.g.
AOT コンパイルとは
- 事前コンパイル
- JITコンパイルと異なり、 JVM 起動前にネイティブバイナリ( = OS が解釈できるコード)にコンパイル
- HotSpot VM でも Java 9 から実験的に導入
JVM のメモリ管理(HotSpot VM Java8 の場合)
生成されたオブジェクトはすべて Heap で管理される。
Java では変数はオブジェクトそのものではなく参照を保持している。逆に参照されていないオブジェクトは削除される。これがGCである。
GC
- GC はとてもコストがかかる
- GC 用のスレッドが必要
- GC 対象のアプリケーションスレッドを止めてしまう
- これがまさに Garbage Collection TIme!!
- GCにより長時間アプリケーションが泊まる現象を Stop the World (STW)と呼ぶ
- GC にはいろいろなアルゴリズムがある
- Mark & Sweep
- Copy
- 世代別
- HotSpotVM での GC の種類は様々…だけどほぼすべて「世代別」が基本となっている
- シリアル型
- パラレル型
- CMS
- G1
Heap の Young領域と Old 領域
- Heapには Young 領域 と Old 領域 がある
- オブジェクトはまず Young 領域に作られる
- Young 領域がいっぱいになってきたら、 Young 領域だけ GC が実行される
- これを マイナーGC という
- GCされずに削除されなかった回数はカウントされる
- マイナーGCを繰り返す中でいつまでも残り、カウントが多くなったオブジェクトは、 Old 領域 に移動する
- もし、Old 領域もいっぱいになってきた場合、 Heap 全域にGCが走る
- これを *メジャー(Full) GC という
