🤖

Java仮想マシン(JVM)を読解しながら理解する #08

に公開

https://zenn.dev/h_kohe/articles/f6a408ada1e07b
こちらの記事の続きです

よんでいる公式ドキュメント
https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-2.html

なんやかんやでついに8回目まですすみました!!
Java25も正式リリースされたので、これから今まで触れたことがない人にもJavaがもっと愛される言語になっていけばいいなーーーと願うばかりです😎!!
Java25についてはきしださんのリリース恒例解説記事が読みやすいと思うのでぜひ!!
https://qiita.com/nowokay/items/7e05b4c42ded043a298a

それでは今回から2.6章です!!

2.6. Frames(フレーム)

Javaの実装をしていくなかであまり馴染みのある言葉ではなかったのですが、ドキュメントには以下内容記載されていました。
JVMにおける フレーム とは、メソッドを実行するための作業単位
データや計算途中の結果を保持
•各フレームは ローカル変数配列 と オペランドスタック を持つ
•ローカル変数 → メソッド引数や一時変数
•オペランドスタック → 式の計算途中の値やメソッド呼び出し時の引数/戻り値
⸻⸻⸻
動的リンクの処理
•各フレームは ランタイム定数プール への参照を持っている
•これにより、実行時にメソッドやフィールドの解決(動的リンク)が可能になる
⸻⸻⸻
メソッドの戻り値を保持
•メソッドが終了すると、フレームは 戻り値(あれば)を前のフレームに返す
•その後、現在のフレームは破棄される
⸻⸻⸻
例外処理の割り当て
•フレームには、実行中に例外がスローされた場合の 例外ハンドラ情報 が含まれる
•該当ハンドラがなければ、フレームをポップして上位のフレームに処理を移す(例外の割り当て)

・・・ここまで要約してきましたが、なんとなくしっくりきたようなきてないような😅
前回記事で記載してきたネイティブメソッドスタックや実行時定数プールとの関連性がしっくりきていないのが原因だと思うのでもう少し詳細を確認して表にしていきます!!

領域 スレッド単位/共有 主な役割 ライフサイクル イメージ
フレーム スレッドごと(JVMスタック内) メソッド実行の作業台(ローカル変数、オペランドスタック、戻り値、例外処理など) メソッド呼び出しごとに生成・終了時に破棄 現場の机:呼び出すたびに机を出して作業し、終わったら片付ける
メソッド領域 全スレッドで共有 クラス情報・バイトコード・定数プールを保持 JVM起動時に作成、終了まで存続 設計図倉庫:プログラム全体のクラスやメソッドの設計図を保管
実行時定数プール クラス単位で共有 定数やメソッド/フィールド参照、動的リンクの辞書 クラスロード時に確保、アンロードで破棄 辞書/索引:設計図に付属する用語集・索引帳
ネイティブメソッドスタック スレッドごと nativeメソッドの実行に使う作業領域 スレッドごとに作成・終了時に破棄 別の机:JVM外(C/C++など)の処理を行う専用の机

ここまで表にして少し解像度が向上したように感じます!!!

2.6.1. Local Variables(ローカル変数)

各フレーム ( §2.6 ) には、ローカル変数と呼ばれる変数の配列が含まれます。

いきなり冒頭から・・・よくわからん・・・😇
chatGPTにこのドキュメントを読解させて質問していくと以下のようなことだと整理してくれました!!

public class Example {
    public int add(int a, int b) {
        int sum = a + b;
        return sum;
    }
}

上記のようなaddメソッドがあった場合いかのような内容(引数・ローカル変数・this)がローカル変数配列に格納される

インデックス 説明
0 this 呼び出したインスタンス参照
1 a 引数 a
2 b 引数 b
3 sum ローカル変数(計算結果保持)

すなわち、Javaの実装時に使用するような参照用配列ではなくJVMがメソッド実行のために内部的に管理している配列であることがわかりました!!
そして・・・・
配列の長さは コンパイル時に決定 され、クラスファイル内にメソッドのコードと一緒に記録される
とのことです。

ちなみにインデックス0に格納される値は以下のように変わるようです!!
インスタンスメソッド → インデックス0は必ず thisが格納される
static メソッド → インデックス0は最初の引数が格納される

保持できるデータ型

•1つのスロット(配列要素)に収まる型 
    boolean, byte, char, short, int, float, reference, returnAddress
•2つのスロットを占有する型
    long, double
•例: double をインデックス n に格納すると、実際には n と n+1 の2つを使う
•ただし、n+1 側から直接読み出すことはできない
→読み出そうとした場合java.lang.VerifyError がスローされる

※スロット:JVMのローカル変数配列やオペランドスタックで使われる最小単位の格納領域のこと

まとめ

●フレームとは
 JVM におけるメソッド実行単位の「作業台」
 メソッド呼び出しごとに作成され、終了時に破棄される
●フレームの中身
 ローカル変数配列(メソッド引数や一時変数を保持する内部配列)
 オペランドスタック(計算途中の値や戻り値を保持するスタック領域)
 ランタイム定数プールへの参照(動的リンクのための辞書)
 戻り値や例外処理の情報
●ローカル変数配列の特徴
 インスタンスメソッド:インデックス0には this が格納される
 static メソッド:インデックス0から引数が格納される
 スロットという単位で管理される
  (int, float, reference などは1スロット、long/double は2スロットを使用)
 long/double の「後ろ側スロット (n+1)」は直接読み込めず、違反すると VerifyError となる

Discussion