🤖

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

に公開

前回記事
https://zenn.dev/h_kohe/articles/2aa5de9cfa795e

※上記記事の続きとして記載しています。

今回は第二章からです!!
https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-2.html

Chapter 2. The Structure of the Java Virtual Machine(Java仮想マシンの構造)

ここに概要が記載されていると思うのですが・・・

Java仮想マシンを正しく実装するには、ファイル形式を読み取りclass、そこに指定された操作を正しく実行できれば十分です。  って書いてありました。。。。
気になっていた、ガベージコレクションアルゴリズムについては
実装者の裁量に委ねられています。 とのこと・・・
どうやら、「どの方式でGCを行うかは自由」という意味でガベージコレクションにも種類がありそうですね・・・!! そこはまた追っていけらたらと思います!!

2.1. The class File Format(classファイル形式

)
この項目では、ざっくりしたclassファイル形式についての概要についての記載で詳しくは第4章で説明とのことです。
この項目での内容は

Javaのclassファイルは、特定のOSやハードウェアに依存せず、JVMで動かすための中立的で汎用的なバイナリ形式である

そして、そのclassファイルは

クラスやインターフェースの表現(名前、継承、フィールド、メソッド、属性)を正確に定義している

とのことでした。

2.2. Data Types(データ型)

  • JVMもJavaと同じく「プリミティブ型」と「参照型」で動いている
  • 型チェックはほとんどコンパイル時に完了しているため、実行時にはタグ付けや動的型チェックは不要
  • JVMの命令は型ごとに専用命令がある(iadd, ladd, fadd, dadd)ので、実行時に型を見て処理を切り替える必要がない
  • 参照型(reference)は「オブジェクトへのポインタ」
  • すべてのオブジェクト(配列含む)は 参照型の値を通じて操作される

が記載されていました。
まず、初見の単語は・・・専用命令でした!!
JVMは「型に応じて異なる命令を使う」ことによって高速で処理できるようになっているようです。
詳細は表にすると以下のような命令があるとのことです。

加算命令一覧(JVMの型別バイトコード)

命令 対応する型 説明
iadd int int型(32bit整数)の加算(integer)
ladd long long型(64bit整数)の加算
fadd float float型(単精度浮動小数点)の加算
dadd double double型(倍精度浮動小数点)の加算

Javaの仕様と同じと思いますが、一応読解します。。。。

参照型(reference)は「オブジェクトへのポインタ」

です。
誤認識がないように、そもそも参照型とは??から開始します。
参照型とは、
「ヒープ上のオブジェクトを指し示すアドレスのような値」
です。
以下のサンプルコードで確認したらイメージがつきやすいと思います!!

String a = new String("hello");
String b = new String("hello");

System.out.println(a == b);       // false → 参照が違う
System.out.println(a.equals(b));  // true  → 文字列の内容が同じ

"=="では変数の中にあるそのもののを比較するため、異なったインスタンス(ヒープ領域で保管されているパスが異なる)のため違う左辺と右辺が異なった値ですと比較されるためfalseになります。

そして、ポインタとは??
「メモリ上のアドレスを直接操作するための変数」
であり、Javaでは禁止されていて近いしい概念として

  • 参照型(reference)は「オブジェクトへのポインタ」

があり、以下のコードだと先ほどの実装とは異なり"=="でもtrueになります。
これがポインタっぽい参照型です!!

String a = new String("hello");
String b = a;

System.out.println(a == b);       // true → 同じインスタンスを参照
System.out.println(a.equals(b));  // true  → 文字列の内容が同じ 

少しイメージつきましたでしょうか??

まとめると・・・・
参照型は、プリミティブ型(int, boolean, doubleなど)とは異なり、“実体そのもの”ではなく、その実体へのアクセス手段を変数として保持する
です!!

なので・・・
JVMは、
プリミティブ型は変数の実体(値そのもの)がローカル変数としてスタックフレーム上に格納し直接操作
それ以外のオブジェクトや配列は、スタックにある参照値を通してアクセス・操作する

種別 スタックに格納されるもの ヒープに格納されるもの 操作方法
プリミティブ型 (int, boolean など) 値そのもの(実体) なし スタック上で直接演算(例:iadd, iload
参照型(オブジェクト、配列など) 参照値(ヒープへのポインタ) オブジェクト本体 参照値を通じてヒープ上の実体を操作

次の項目はプリミティブ型と値なのですが、各プリミティブ型の話になるため長そうなので次回にまわします!!

まとめ

  • JVMは抽象マシンであり、実装者は内部構造ガベージコレクションや最適化を自由に工夫できる。
  • ClassファイルはOS/CPU非依存のバイナリ形式で、クラス/インターフェースの構造(名前, 継承, メンバー, 属性など)を正確に表現する。
  • JVMの型は プリミティブ / 参照 の2系統ある。
  • 型チェックはほぼコンパイル時に実施される想定で、JVMは実行時に型タグを見分ける必要がない。
  • 命令は型付き(iadd, ladd, fadd, dadd など)**のため、実行時分岐不要で効率的。
  • 参照値はオブジェクトへのポインタ的概念、クラスインスタンスも配列もオブジェクトであり、すべて参照型経由で操作される。

得た知見

  • ガベージコレクションは「必ずこう実装せよ」と仕様はなく、どうやってそれを実現するかは実装者に委ねられている。
  • 型安全はコンパイル時に前倒しされ、JVM命令は型ごとに分かれている=高速化と安全性の鍵。
  • JVMは、Javaの構文・型システム・実行モデルを効率的に処理できるよう設計された実行環境である。

Discussion