記事ディレクトリ
1 JVM と Java アーキテクチャ
1.0 Java 開発の主なイベント
2000 年に JDK 1.3 がリリースされ、Java Hot Spot Virtual Machine が正式にリリースされ、Java のデフォルトの仮想マシンになりました。
2006 年に JDK 6 がリリースされました。同年、Javaがオープンソース化され、OpenJDKが設立された。当然、Hotspot 仮想マシンも OpenJDK のデフォルトの仮想マシンになりました。
2008 年に Oracle は BEA を買収し、JRockit 仮想マシンを取得しました。
2010 年に Oracle は Sun を買収し、Java の商標と HotSpot 仮想マシンを取得しました。
2011 年に、G1 ガベージ コレクターが JDK 7 で正式に有効になりました。
2017 年に、G1 が JDK 9 のデフォルト GC となり、CMS に置き換わりました。IBM は J9 仮想マシンもオープンソース化しました。
2018 年、JDK 11 LTS バージョンでは革新的な ZGC がリリースされ、jdk ライセンスが調整されました。
2019 年に JDK 12 がリリースされ、Shenandoah gc が追加されました。
1.1 仮想マシンと Java 仮想マシン
仮想マシンは仮想コンピュータであり、一連の仮想コンピュータ命令を実行するために使用されるソフトウェアです。大きく分けてシステム仮想マシンとプログラム仮想マシンに分けられます。
Visual Box と VMWare はシステム仮想マシンであり、物理コンピューターを完全にエミュレーションし、実行可能な完全なオペレーティング システム ソフトウェア プラットフォームを提供します。Java 仮想マシンは典型的なプログラム仮想マシンであり、単一のコンピュータ プログラムを実行するように特別に設計されています。
仮想マシンの種類に関係なく、そこで実行されるソフトウェアは、仮想マシンによって提供されるリソースに限定されます。
Java 仮想マシンは、バイトコードを実行する仮想コンピュータです (このバイトコードは Java 言語または他の言語で生成できます)。
1.3 JVMの全体構造
1.4 Javaコードの実行処理
Java ソース コード (xxx.java)
コンパイル (フロントエンド コンパイル): 字句解析、構文解析、文法/抽象構文ツリー、意味解析、注釈抽象構文ツリー、およびバイトコード ジェネレーター。
バイトコード (xxx.class)
Java 仮想マシン: クラス ローダー、バイトコード ベリファイア、バイトコード インタプリタ、および JIT コンパイラ バイナリ
命令
オペレーティング システム
1.5 JVM アーキテクチャ モデル
Java コンパイラによって入力される命令ストリームは、基本的にはスタックベースの命令セット アーキテクチャであり、もう 1 つの命令アーキテクチャはレジスタベースの命令アーキテクチャです。
スタック アーキテクチャの特徴:
- 設計と実装が容易で、リソースに制約のあるシステムに適しています。
- レジスタ割り当ての問題を回避します。割り当てにはゼロ アドレス命令を使用します。
- 命令ストリーム内のほとんどの命令はゼロアドレス命令であり、それらの実行は演算スタックに依存します。命令セットが小さく (単一命令が短く)、コンパイラーの実装が簡単です。
- ハードウェアのサポートが不要になり、移植性が向上し、クロスプラットフォームの実装が向上します。
レジスタ アーキテクチャの特徴:
5. 典型的なアプリケーションは、x86 バイナリ命令セット、つまり従来の PC および Android の Davlik 仮想マシンです。
6. 命令セット アーキテクチャはハードウェアに完全に依存しており、移植性が劣ります。
7. 優れたパフォーマンスと効率的な実行。
8. 操作を完了するために必要な命令が少なくなります。
ほとんどの場合、レジスタ アーキテクチャに基づく命令セットは、1 アドレス命令、2 アドレス命令、および 3 アドレス命令によって支配される傾向があります。スタックベースの命令アーキテクチャは、多くの場合、ゼロアドレス命令が大半を占めます。
例: 2 つの命令アーキテクチャで 2+3 を実現
iconst_2 // 常量2入栈
istore_1
iconst_3 // 常量3入栈
istore_2
iload_1
iload_2
iadd // 常量2,3出栈,执行相加
istore_0 // 结果5入栈
mov eax,2 // eax 初始值设置为2
add eax,3 // 将寄存器内的值+3
1.6 JVM ライフサイクル
[起動]
Java 仮想マシンの起動は、仮想マシンの特定の実装によって指定されるブートストラップ クラス ローダーを通じて初期クラス (初期クラス) を作成することによって行われます。
[実行]
実行中の Java 仮想マシンには、Java プログラムを実行するという明確なタスクがあります。
プログラムの実行開始時に実行され、プログラムの終了時に停止します。
Javaプログラムを実行する際、実際に実行されるのはJava仮想マシンのプロセスです。
【やめる】
- プログラムは正常に終了します
- プログラムの実行中に例外またはエラーが発生し、異常終了します。
- オペレーティング システムのエラーにより、Java 仮想マシン プロセスが終了しました。
- スレッドは、Runtime クラスまたは System クラスの exit メソッドを呼び出すか、Runtime クラスの halt メソッドを呼び出します。Java セキュリティ マネージャーも、この exit または halt 操作を許可します。
- JNI 仕様では、JNI 呼び出し API を使用して Java 仮想マシンをロードまたはアンロードすると、Java 仮想マシンが終了すると説明されています。
1.7 JVM開発の歴史
[Sun Classic VM]
1996 年に、JDK1.0 によって最初の商用 Java 仮想マシンである Sun Classic VM がリリースされました。jdk 1.4 では削除されました。
この VM はインタープリタのみを提供します。
jit コンパイラーを使用したい場合は、プラグインする必要があります。JIT が使用されると、JIT が仮想マシンの実行システムを引き継ぎます。通訳はもう機能しません。インタプリタとコンパイラは連携しません。
この仮想マシンは HotSpot 仮想マシンに組み込まれています。
【正確なVM】
- この仮想マシンは、jdk1.2 を使用する場合に提供されます。
- 正確なメモリ管理: 正確なメモリ管理。仮想マシンは、メモリ内の特定の場所にどのような種類のデータがあるかを知ることができます。
- これは、最新の高性能仮想マシンのプロトタイプを備えています。(1) ホットスポット検出 (2) コンパイラとインタープリタの混合動作モード。
- Solaris プラットフォームでは短期間のみ使用されます。最終的にはホットスポットに置き換えられました。
【ホットスポットVM】
- jdk1.3 がデフォルトの仮想マシンになりました。
- 市場で絶対的な地位を占め、格闘技界を制覇する。Oracle jdk と OpenJDK は両方ともデフォルトの仮想マシンです。
- ホットスポット検出テクノロジーを使用します。カウンタを通じて最もコンパイル値が高いコードを見つけ、スタック上でジャストインタイム コンパイルまたは置換をトリガーします。コンパイラーとインタープリターは連携して、最適な応答時間と最高の実行パフォーマンスのバランスをとります。
【JRockit VM】
- サーバー側アプリケーションに焦点を当てます。起動速度には注意を払っておらず、内部にインタプリタも含まれておらず、すべてのコードはジャストインタイム コンパイラによってコンパイルおよび実行されます。
- 最速のJVM
【J9】
- アイ・ビー・エム株式会社
- 最速の仮想マシンとして知られています。その主な理由は、IBM 製品でより適切に動作するためです。
【VMブルー】
- 特定のハードウェア プラットフォームにバインドされ、ソフトウェアおよびハードウェアと連携する専用の仮想マシン。
- 各 Azul VM インスタンスは、少なくとも数十の CPU と数百 GB のメモリのハードウェア リソースを管理でき、膨大なメモリ範囲内で制御可能な GC 時間を実現するガベージ コレクターや、独自のハードウェアによって最適化されたスレッド スケジューリングなどの優れた機能を提供します。
【リキッドVM】
- 特定のハードウェア プラットフォームにバインドされ、ソフトウェアおよびハードウェアと連携する専用の仮想マシン。
- Liquid VM はオペレーティング システムのサポートを必要とせず、スレッド スケジューリング、ファイル システム、ネットワーク サポートなどの専用オペレーティング システムに必要な機能をそれ自体で実装します。
【アパッチハーモニー】
- その Java クラス ライブラリ コードは Android SDK に吸収されます。
【マイクロソフトJVM】
- 当初は IE ブラウザで Java アプレットをサポートすることを目的としていたため、Windows プラットフォーム上でしか動作できませんでしたが、当時 Windows プラットフォーム上で最高のパフォーマンスを発揮した Java VM でした。
- 1997 年、この VM は商標権侵害、不正競争、その他の犯罪により店頭から撤去されました。
【タオバオJVM】
- Ali は OpenJDK に基づいて独自にカスタマイズされた AlibabaJDK を開発しました
- これは、Java 仮想マシンの高度にカスタマイズされたオープンソースの高性能サーバー バージョンです。
- GCIH (GC Invisible Heap) テクノロジーはオフヒープを実現します。つまり、ライフサイクルの長い Java オブジェクトをヒープからヒープ外に移動し、GC が GCIH 内の Java オブジェクトを管理できないようにすることで、GC リサイクルの頻度を減らし、GC のリサイクル効率を向上させます。
- GCIH のオブジェクトは、複数の仮想マシン プロセス間で共有できます。
- Intel の CPU に大きく依存しているため、互換性が失われ、パフォーマンスが向上します。
【ダルヴィクVM】
- Goolgeによって開発され、Androidシステムに適用され、Android2.2でJITが提供されました
- Dalvik VM は仮想マシンとしか呼ぶことができず、「Java 仮想マシン」とは呼べず、Java 仮想マシンの仕様には準拠していません。
- dex (Dalvik Executable) ファイルが実行されるため、より効率的です。dex ファイルは、classes ファイルから変換できます。
- レジスタベースの命令アーキテクチャ
- Android 5.0 では、Dalvik VM が、Ahead of Time Compilation (AOT) をサポートする ART VM に置き換えられます。
【聖杯VM】
- 2018.04 Oracle Labs公開
- どこでもプログラムをより高速に実行
- 「あらゆる言語」の実行プラットフォームとして使用できるクロス言語フルスタック仮想マシン。c と cpp を含みます。
2 クラスローディングサブシステム
2.1 クラスローダー
複数の ClassLoader が存在する場合があります。
ファイルヘッダーの特定のファイル識別子は「coffee baby」です。
すべてのクラス ローダーは継承関係ではありませんが、階層関係とみなすことができます。最上位のブートストラップが常にオブジェクトを最初にロードし、ロードできないオブジェクトは後で順番にロードされます。
[ブートストラップ クラス ローダー]
ブートストラップ ローダーは、jre/lib/rt.jar
パス内のコンテンツ、オブジェクト クラス、文字列クラス、ArrayList などをすべてロードします。起動クラスローダーのロードパスを表示するresources.jar
sun.boot.class.path
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
拡張クラスとアプリケーション クラス ローダーをロードし、それらを親クラス ローダーとして指定します。
セキュリティ上の理由から、ブートストラップ起動クラスローダーは、パッケージ名が java、javax、sun などで始まるクラスのみをロードします。
[拡張クラスローダー]
拡張クラスローダーは、jre/lib/ext/*.jar のコンテンツをロードします。
ユーザーが作成した jar パッケージも ext ディレクトリに配置されている場合は、ext クラス ローダーによって自動的にロードされます。
拡張クラスローダーのロードパスを表示する
String extDirs = System.getProperty("java.ext.dirs");
String dirArr = extDirs.split(";");
[システム クラス ローダー]
アプリケーション クラス ローダーは、Java コードでカスタム オブジェクトをロードします。
環境変数 classpath またはシステム プロパティ java.class.path で指定されたパスにクラス ライブラリをロードします。
システムクラスローダはプログラムのデフォルトのクラスローダであり、一般にJavaアプリケーションのクラスはこれによって完成されます。
このようなローダーは次のようにして取得できます。ClassLoader.getSystemClassLoader()
検証ユースケースプログラム
package org.example.classloader;
import com.sun.nio.zipfs.JarFileSystemProvider;
public class ClassLoaderTest {
public static void main(String[] args) {
Object o = new Object();
System.out.println(o.getClass().getClassLoader()); // bootstrap class loader 获得不到的,返回值为null
System.out.println("==========================================");
MyObject myObject = new MyObject();
System.out.println(myObject.getClass().getClassLoader());
System.out.println(myObject.getClass().getClassLoader().getParent());
System.out.println(myObject.getClass().getClassLoader().getParent().getParent());
System.out.println("==========================================");
JarFileSystemProvider provider = new JarFileSystemProvider();
System.out.println(provider.getClass().getClassLoader());
System.out.println(provider.getClass().getClassLoader().getParent());
}
}
class MyObject {
}
null
==========================================
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@14ae5a5
null
==========================================
sun.misc.Launcher$ExtClassLoader@14ae5a5
null
JVM 仕様の観点から見ると、クラス ローダーにはブートストラップ クラスローダーとユーザー定義クラスローダーの 2 種類があります。Java 仮想マシンの仕様では、ClassLoader から派生したすべてのクラス ローダーはカスタム クラス ローダーとして分類されます。
classLoaderの取得方法は以下の通りです。
- クラスの ClassLoader を取得する
clazz.getClassLoader();
- 現在のスレッドコンテキストの ClassLoader を取得します。
Thread.currentThread().getContextClassLoader();
- システムの ClassLoader を取得する
ClassLoader.getSystemClassLoader();
- 呼び出し元の ClassLoader を取得する
DriverManger.getCallerClassLoader();
2.2 ユーザー定義のクラスローダー
2.2.1 カスタム クラス ローダーが必要な理由
- 分離されたクラスローダー
- クラスのロード方法を変更する
- 拡張ロードソース
- ソースコードの漏洩を防ぐ
2.2.2 カスタムクラスローダーの実装手順
- 抽象クラスを継承するには、メソッドに
java.lang.ClassLoader
カスタム クラス ロード ロジックを入れることをお勧めします。findClass()
- 特に複雑な要件がない場合は、URLClassLoader を直接継承できます。これにより、findClass メソッドやバイトコード ストリームを取得する方法を自分で記述する必要がなくなり、カスタム クラス ローダーの記述がより簡潔になります。
2.3 親の委任メカニズム
覚え方:「突き出す」
親の委任メカニズムが Java コードのセキュリティを保証することを証明するために事例が使用されます。
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application
親の委任メカニズムの利点:
- クラスの重複ロードを避ける
- プログラムのセキュリティを保護し、コア API が任意に改ざんされるのを防ぎます。
2.3 クラスロード処理
読み込み→[検証→準備→解決]→初期化
------------------------【リンク作業です】
【ロード】
- このクラスを定義するバイナリ バイト ストリームを完全修飾名で取得します。
- このバイトストリームによって定義された静的ストレージ構造をメソッド領域のランタイムデータ構造に変換します。
- このクラスを表す java.lang.Class オブジェクトは、メソッド領域内のこのクラスのさまざまなデータへのアクセス エントリとしてメモリ上に生成されます。
.class ファイルのソース:
- ローカルシステム内で
- ネットワーク取得、一般的なシナリオ: Web アプレット。
- zip パッケージからの読み取りは、jar および war からの読み取りの基礎です。
- 実行時計算の生成で最もよく使用されるのは、動的プロキシ テクノロジです。
- 他のファイルによって生成される、一般的なシナリオ: jsp アプリケーション
- 独自のデータベースから .class ファイルを抽出します。比較的まれです。
- 暗号化されたファイルから取得されます。クラス ファイルの逆コンパイルを防ぐための一般的な保護手段です。
【確認】
- その目的は、クラス ファイルに含まれる情報が現在の仮想マシンの要件を満たしていることを確認し、ロードされたクラスの正確性を保証し、仮想マシン自体の安全性を危険にさらさないことです。
- 主にファイル形式検証、メタデータ検証、バイトコード検証、シンボル参照検証の4種類の検証が行われます。
【準備】
- クラス変数にメモリを割り当て、クラス変数のデフォルトの初期値、つまりゼロ値を設定します。
- Final で変更された静的変数の場合は、すでに定数になっており、コンパイルフェーズで値が代入されており、準備フェーズで初期化が表示されます。
- ここでは、初期化ではインスタンス変数は割り当てられず、クラス変数はメソッド領域に割り当てられ、インスタンス変数はオブジェクトとともにJavaヒープに割り当てられます。
【分析】
- 定数プールで使用されるシンボルを直接参照に変換するプロセス。
- 実際、解析操作には初期化後の JVM の実行が伴うことがよくあります。
[初期化] 4. 初期化フェーズは、クラス コンストラクター メソッド
を実行するプロセスです。クラス コンストラクターとは異なります (関連付け: コンストラクターは仮想マシンの観点からのものです) 5.定義する必要はありません。javac コンパイラーによって自動的に収集されたクラス内のすべてのクラス変数の代入アクションと静的コード ブロック内のステートメントの組み合わせです。クラス コンストラクター メソッドの命令は、ソース ファイルに記述されている順序で実行されます。6. クラスに親クラスがある場合、JVM はサブクラスの実行前に親クラスのメソッドが実行されていることを確認します。7. 仮想マシンは、クラスのメソッドがマルチスレッド環境で同期的にロックされていることを確認する必要があります。8. <clinit>()
<clinit>()
<init>()
<clinit>()
<clinit>()
<clinit>()
<clinit>()
public class ClassInitTest {
static {
number = 20;
// 报错:非法的前向引用
// System.out.println(number);
}
// prepare : number = 0;
// initial : 20 ---> 10
private static int number = 10;
public static void main(String[] args) {
System.out.println(ClassInitTest.number); // 10
}
}
2.4 その他
JVM では、2 つのクラス オブジェクトが同じクラスであるかどうかを示すために必要な条件が 2 つあります。
- クラスの完全なクラス名は、パッケージ名も含めて一貫している必要があります。
- このクラスをロードする ClassLoader (ClassLoader インスタンス オブジェクトを参照) は同じである必要があります
JVM は、型が起動クラス ローダーによってロードされたか、ユーザー クラス ローダーによってロードされたかを認識する必要があります。型がユーザー クラス ローダーによってロードされる場合、JVM はクラス ローダーへの参照を型情報の一部としてメソッド領域に保存します。ある型から別の型への参照を解決する場合、JVM は両方の型のクラス ローダーが同じであることを確認する必要があります。
Java プログラムによるクラスの使用は、アクティブな使用とパッシブな使用に分けられます。
アクティブな使用法:
1 クラスのインスタンスを作成します。 2クラスまたはインターフェイスの静的変数にアクセスするか、
静的変数に値を割り当てます。 3
クラスの静的メソッドを呼び出します。4リフレクション ( Class.initialization
など)。java.lang.invoke.MethodHandle
REF_getStatci
REF-putStatic
REF-invokeStatic
参考文献
[1] https://www.bilibili.com/video/BV1jJ411t71s?p=5&spm_id_from=pageDriver&vd_source=f4dcb991bbc4da0932ef216329aefb60