免責事項:JVMシリーズのブログは私自身のJVM研究ノートです。類似点はまったく偶然です。
/**
* @startTime 2021-03-13 08:30
* @endTime 2021-03-13 14:30
* @start P26内存结构概述
* @end P35双亲委派机制
* @efficiency (P35-P25)/1 = 10 * 10.8 = 108分钟/天
* @needDays 3841/108 = 36天
* @overDay 2021-03-13 + 36天 = 2021-04-17
*/
第1章クラスローダー
1つは、クラスローダーサブシステムの役割です。
クラスローダーサブシステムは、ファイルシステムまたはネットワークからクラスファイルをロードする役割を果たします。クラスファイルには、ファイルの先頭に特定のファイル識別子があります。
ClassLoaderは、クラスファイルのロードのみを担当します。実行できるかどうかは、実行エンジンによって決定されます。
ロードされたクラス情報は、メソッド領域と呼ばれるメモリ空間に格納されます。クラス情報に加えて、メソッド領域には実行時定数プール情報も格納され、文字列リテラルと数値定数も含まれる場合があります(定数情報のこの部分は、クラスファイル内の定数プール部分のメモリマッピングです)。
次に、クラスの読み込みプロセス
1.ロード
- クラスの完全修飾名を使用して、このクラスを定義するバイナリバイトストリームを取得します
- このバイトストリームによって表される静的ストレージ構造をメソッド領域のランタイムデータ構造に変換します
- メソッド領域でこのクラスのさまざまなデータへのアクセスエントリとして、メモリ内のこのクラスを表すjava.lang.Classオブジェクトを生成します。
2.リンク
(1)確認する
- 目的は、クラスファイルのバイトストリームに含まれる情報が現在の仮想マシンの要件を満たしていることを確認し、ロードされたクラスの正確性を確認し、仮想マシン自体のセキュリティを危険にさらさないようにすることです。
- 検証には主に、ファイル形式の検証、メタデータの検証、バイトコードの検証、シンボル参照の検証の4種類があります。
(2)準備する
- クラス変数にメモリを割り当て、クラス変数のデフォルトの初期値を設定します
- 最終的に変更された静的はここには含まれません。これは、最終がコンパイル中に割り当てられ、初期化が準備フェーズで表示されるためです。
- ここでは、インスタンス変数は割り当てられず、初期化されません。クラス変数はメソッド領域に割り当てられ、インスタンス変数はオブジェクトとともにヒープに割り当てられます。
(3)分析
- 定数プール内のシンボル参照を直接参照に変換するプロセス
- たとえば、静的コードブロックと静的変数の表示割り当て
- 実際、解析操作には、初期化が実行された後のJVMの実行が伴うことがよくあります。
- シンボル参照は、参照されるターゲットを説明するための一連のシンボルです。シンボル参照のリテラル形式は、「Java仮想マシン仕様」のクラスファイル形式で明確に定義されています。直接参照は、ターゲットへのポインタ、相対オフセット、またはターゲットに間接的に配置されたハンドルです。
- 解析アクションは、主にクラスまたはインターフェイス、フィールド、クラスメソッド、インターフェイスメソッド、メソッドタイプなどを対象としています。定数プール内のCONSTANT_Filedref_info、CONSTANT_Class_info、CONSTANT_Methodref_infoなどの場合。
3.初期化
- 初期化フェーズは、クラスコンストラクタメソッド<clinit>()を実行するプロセスです。
- このメソッドを定義する必要はありません。javacコンパイラは、クラス内のすべてのクラス変数の割り当てアクションを自動的に収集し、静的コードブロック内のステートメントをマージします。
- コンストラクターメソッドの命令は、ステートメントがソースファイルに表示される順序で実行されます。
- <clinit>()メソッドは、クラスコンストラクターとは異なります。コンストラクターは、仮想マシンの観点からは<init>()です。
- クラスに親クラスがある場合、JVMは、サブクラスの<clinit>()が実行される前に、親クラスの<clinit>()が実行されたことを確認します。
- 仮想マシンは、クラスの<clinit>()メソッドが同期され、複数のスレッドでロックされていることを確認する必要があります
3つのコード例
[仮想マシンは、クラスの<clinit>()メソッドが同期され、マルチスレッドでロックされていることを確認する必要があります]のコード例を確認します。
package com.guor.jvm;
public class DeadThreadTest {
public static void main(String[] args) {
Runnable r = () -> {
System.out.println(Thread.currentThread().getName() + "开始");
DeadThread dead = new DeadThread();
System.out.println(Thread.currentThread().getName() + "结束");
};
Thread t1 = new Thread(r,"线程1");
Thread t2 = new Thread(r,"线程2");
t1.start();
t2.start();
}
}
class DeadThread{
static {
if(true){
System.out.println(Thread.currentThread().getName() + "初始化当前类");
while (true){
}
}
}
}
仮想マシンは、クラスの<clinit>()メソッドが同期され、複数のスレッドでロックされていることを確認する必要があります
第四に、クラスローダーの分類
JVMクラスローダーには、BootstrapClassLoaderとUser-DefinedClassLoaderの2種類があります。
抽象クラスClassLoaderから派生したすべてのクラスローダーは、カスタムクラスローダーとして分類されます。
1.クラスローダーを起動します(ブートクラスローダー)
- スタートアップクラスローダーはC / C ++言語で実装され、JVM内にネストされています
- Javaのコアクラスライブラリはすべて、Stringなどのブートクラスローダーを使用してロードされます。
- 親ローダーなし
- これは、拡張クラスローダーとアプリケーションクラスローダーの親クラスローダーです。
- セキュリティ上の理由から、Bootstrapスタートアップクラスローダーは、パッケージ名がjava、javax、sunなどで始まるクラスのみをロードします。
2.クラスローダーを拡張します
- Java言語で書かれています
- ClassLoaderクラスから派生
- 親クラスローダーはスタートアップクラスローダーです
- java.ext.dirsシステムプロパティで指定されたディレクトリからクラスライブラリをロードするか、JDKインストールディレクトリのjre / lib / extサブディレクトリ(拡張ディレクトリ)からクラスライブラリをロードします。ユーザーが作成したjarがこのディレクトリに配置されると、拡張クラスローダーによって自動的にロードされます。
3.アプリケーションクラスローダー(システムクラスローダー)
- Java言語で書かれています
- ClassLoaderクラスから派生
- 親クラスローダーは拡張クラスローダーです
- これは、環境変数classpathまたはシステムプロパティjava.class.pathで指定されたパスの下にクラスライブラリをロードする役割を果たします。
- このクラスローダーは、プログラムのデフォルトのクラスローダーです。一般的に、Javaアプリケーションクラスはそれによってロードされます。
- クラスローダーは、ClassLoader.getSystemClassLoader()メソッドを介して取得できます。
5.親の委任メカニズム
1.クラスローダーがクラスロード要求を受信した場合、最初にロードしません。20は、この要求を親クラスのローダーに委任して実行します。
2.親クラスローダーにまだ親クラスローダーがある場合、それはさらに上向きに、再帰的に順番に委任され、要求は最終的に最上位のスタートアップクラスローダーに到達します。
3.親クラスローダーがロードタスクを完了できる場合、正常に戻ります。親クラスがロードタスクを完了できない場合、子ローダーはパラメーターを使用して自身をロードします。これは親委任メカニズムです。
6、サンドボックスセキュリティメカニズム
セブン、クラスローダーへの参照
JVMは、タイプがスタートアップクラスローダーによってロードされるか、ユーザークラスローダーによってロードされるかを認識している必要があります。型がユーザークラスローダーによってロードされる場合、JVMはこのクラスローダーへの参照を型情報の一部としてメソッド領域に保存します。あるタイプから別のタイプへの参照を解決する場合、JVMは、これら2つのタイプのクラスローダーが同じであることを確認する必要があります。
8.クラスの能動的および受動的な使用
積極的な使用の7つのケース:
- クラスのインスタンスを作成します
- 特定のクラスまたはインターフェイスの静的変数にアクセスするか、静的変数に値を割り当てます
- クラスの静的メソッドを呼び出す
- 反射
- クラスのサブクラスを初期化します
- Java仮想マシンの起動時に起動クラスとしてマークされるクラス
- JDK7が提供する動的言語サポート:java.lang.invoke.MethodHandlerインスタンスの分析結果
上記の7つの場合を除いて、Javaクラスを使用する他のメソッドは、クラスの受動的な使用と見なされ、クラスの初期化にはつながりません。
/**
* @startTime 2021-03-14 08:30
* @endTime 2021-03-14 11:30
* @start P37沙箱安全机制
* @end P43解决PC寄存器的两个面试问题
* @efficiency (P42-P25)/2 = 9 * 10.8 = 97分钟/天
* @needDays 3841/97 = 40天
* @overDay 2021-03-13 + 40天 = 2021-04-21
*/
第2章ランタイムデータ領域
1.ランタイムデータ領域の内部構造
2、PCレジスタ
1.コンセプト
JVMのプログラムカウンタレジスタでは、レジスタの名前はCPUのレジスタに由来し、レジスタは命令に関連するオンサイト情報を格納します。CPUは、データをレジスタにロードすることによってのみ実行できます。
ここでは、広い意味での物理レジスタではなく、PCカウンタ(または命令カウンタ)(プログラムフックとも呼ばれます)に変換する方が適切な場合があり、不必要な誤解を招くことは容易ではありません。JVMのPCレジスタは、物理PCレジスタの抽象的なシミュレーションです。
2.機能
PCレジスタは、次の命令、つまり実行される命令コードを指すアドレスを格納するために使用されます。実行エンジンは次の命令を読み取ります。
3.PCレジスタの概要
JVM仕様では、各スレッドには、スレッド専用の独自のプログラムカウンターがあり、そのライフサイクルはスレッドのライフサイクルと一致しています。
4. PCレジスタを使用してバイトコード命令アドレスを格納する用途は何ですか?
CPUは常にスレッドを切り替える必要があるため、この時点で切り替えた後、CPUは実行を続行する場所を認識します。
JVMのバイトコードインタプリタは、PCレジスタの値を変更することにより、次に実行するバイトコード命令を決定する必要があります。
5.CPUタイムスライス
/**
* @startTime 2021-03-16 21:00
* @endTime 2021-03-16 22:30
* @start P44虚拟机栈
* @end P52操作数栈
* @efficiency (P52-P25)/4 = 6.75 * 10.8 = 72.9分钟/天
* @needDays 3841/72.9 = 53天
* @overDay 2021-03-13 + 53天 = 2021-05-03
*/
第三に、仮想マシンスタック
1.仮想マシンスタックの背景
クロスプラットフォーム設計のため、Java命令はスタックに基づいて設計されます。プラットフォームが異なれば、CPUアーキテクチャも異なるため、レジスタベースになるように設計することはできません。
利点は、クロスプラットフォームであり、命令セットが小さく、コンパイラーの実装が容易であるということですが、欠点は、パフォーマンスが低下し、同じ機能を実現するためにより多くの命令が必要になることです。
2.Java仮想マシンスタックとは何ですか
Java仮想マシンスタックは、午前中はJavaスタックとも呼ばれます。各スレッドが作成されると、Javaメソッド呼び出しに対応するフレームのスタックを内部に格納する仮想マシンスタックが作成されます。
ライフサイクルはスレッドのライフサイクルと同じです。
Javaプログラムの操作を担当し、ローカル変数テーブルとメソッドの部分的な結果を保存し、メソッドの呼び出しと戻りに参加します。
3.スタックに表示される可能性のある例外
(1)固定サイズのJava仮想マシンスタックが使用されます。スレッドによって要求されたスタック容量がJava仮想マシンスタックで許可されている最大容量を超えると、Java仮想マシンはStackOverflowError例外をスローします。
(2)動的スタックサイズでは、十分なメモリを適用できない場合、OutOfMemoryErrorがスローされます。
4.スタックの動作原理
5.スタックフレームの内部構造
第四に、ローカル変数テーブル
1.ローカル変数テーブルのサイズ
ローカル変数テーブルのサイズはコンパイル時に決定され、ローカル変数テーブルのサイズはメソッドの実行中に変更されません。
2、スロット
ローカル変数テーブルの記憶単位はスロット(可変スロット)です。
コンパイル中に認識できるさまざまな基本データ型とアプリケーション型は、ローカル変数テーブルに格納されます。
ローカル変数テーブルでは、32ビットタイプは1つのスロットのみを占有し、64ビットタイプ(long + double)は2つのスロットを占有します。
現在のスタックフレームがコンストラクターまたはインスタンスメソッドによって作成された場合、これを参照するオブジェクトはインデックス0のスロットに格納され、残りのパラメーターは引き続きテーブルの順序で並べ替えられます。これがコンストラクターとインスタンスメソッドで呼び出すことができる本当の理由ですが、これはローカル変数テーブルに格納されているため、静的メソッドでは呼び出すことができません。
3.補足説明
ローカル変数テーブルで直接または間接的に参照されるオブジェクトがリサイクルされない限り、ローカル変数テーブルの変数は重要なガベージコレクションルートノードです。
5、オペランドスタック
1.オペランドスタックは配列として実装され、後入れ先出し、順番に格納され、インデックスが付けられます。
2.メソッド実行の過程で、バイトコード命令に従ってスタックを操作し、データをスタックに書き込むか、データを抽出します。つまり、スタックにプッシュし、ステーションからポップします。
3.オペランドスタックは、主に計算プロセスの中間結果を保存するために使用されると同時に、計算プロセスの変数の一時ストレージスペースとして使用されます。
4.オペランドスタックは、JVM実行エンジンの作業領域です。メソッドが最初に実行されると、新しいスタックフレームも作成され、このメソッドのオペランドスタックは空になります。
5.オペランドスタックは配列によって実装されますが、オペランドスタックはデータアクセス用のインデックスへのアクセスには使用されませんが、標準のスタッキングおよびポップ操作を介してのみアクセスできます。
過去のハイライト: