blog

JVMを知る

クラスが仮想マシンのメモリにロードされてからメモリからアンロードされるまでのライフサイクル全体は、ロード、検証、準備、解析、初期化、使用、アンロードの7つのフェーズで構成されます。クラス・ロードのプロ...

Apr 21, 2020 · 15 min. read
シェア

jvm,jre,jdk

JDK 8 is a superset of JRE 8, and contains everything that is in JRE 8, plus tools such as the compilers and debuggers necessary for developing applets and applications. JRE 8 provides the libraries, the Java Virtual Machine (JVM), and other components to run applets and applications written in the Java programming language. Note that the JRE includes components not required by the Java SE specification, including both standard and non-standard Java components.
JDK 8JRE 8は、Javaプログラミング言語で書かれたアプレットやアプリケーションを実行するためのライブラリ、Java仮想マシン、その他のコンポーネントを提供する。JREは、Javaプログラミング言語で書かれたアプレットやアプリケーションを実行するためのライブラリ、Java仮想マシン、その他のコンポーネントを提供する。JREには、標準および非標準のJavaコンポーネントを含め、Java SE仕様で要求されていないコンポーネントも含まれていることに注意。

クラス・ファイルへのソース・コード

ソースコード

public class Person{
	
	private Integer age;
	
	private String name;
	
	private Integer getAge(){
		return age;
	}
	
	private String getName(){
		return name;
	}
	
	private void setAget(Integer age){
		this.age=age;
	}
	
	private void setName(String name){
		this.name=name;
	}
}

javac Person.java -> Person.class

コンパイルプロセス:字句解析→構文解析→意味解析と中間コード生成→最適化→ターゲットコード生成

クラス・ファイル

マジックナンバーとバージョン番号

ClassFile {
 u4 magic;
 u2 minor_version;
 u2 major_version;
 u2 constant_pool_count;
 cp_info constant_pool[constant_pool_count-1];
 u2 access_flags; //アクセス・フラグ
 u2 this_class;// 
 u2 super_class;//親クラスのインデックス
 u2 interfaces_count;//インターフェース・インデックス
 u2 interfaces[interfaces_count];
 u2 fields_count;// 
 field_info fields[fields_count];
 u2 methods_count;//メソッド・テーブルのコレクション
 method_info methods[methods_count];
 u2 attributes_count;//プロパティ・テーブル・コレクション
 attribute_info attributes[attributes_count];
}

各Classファイルの最初の4バイトはマジック・ナンバーと呼ばれ、その唯一の目的は、そのファイルがVMに受け入れられるClassファイルであるかどうかを判断することです。

5バイト目と6バイト目がマイナー・バージョン番号で、7バイト目と8バイト目がメジャー・バージョン番号です。

ultraEditによって開かれたPerson.classファイルの最初の行のスクリーンショットは次のとおりです。

上記の図から分析することができます、マジックナンバーの値は、CAFEBASE、MinorVersionは0、メジャーバージョン34 10進数52、つまり、java8です。

定数プール

メジャーバージョン番号とマイナーバージョン番号の直後には、クラスファイルのリソースリポジトリに例えられるコンスタントプールエントリがあります。

上記の Person.class ファイルからわかるように、定数プールのサイズは 001D で、これは 10 進数で 29 です。

この容量カウントは1から始まります。下図に示すように、定数プールの容量は16進数で0x0013、10進数で19となり、インデックスが1から18までの18個の長い定数が存在します。 クラス・ファイル・フォーマットの仕様が策定された当時、設計者は0番目の定数を空にしておくことに特別な配慮をしていました。そうする目的は、後に定数プールのインデックス値を指すデータの一部が、特定のケースで「定数プールのどの項目も参照しない」という意味を表現する必要が生じた場合、インデックス値を0に設定して表現することができるからです。"定数プールのどの項目も参照しない"、これを示すためにインデックス値を0に設定することができます。 javap -verbose disambiguationはアセンブリ命令を生成し、以下のイメージはプール内のPerson定数の数を28個示しています。

クラスのロード

クラスのライフサイクル全体は、VMのメモリにロードされてからメモリからアンロードされるまで、ロード、検証、準備、解析、初期化、使用、アンロードの7つのフェーズで構成されます。その順序を下図に示します:

クラス・ロードのプロセスは、ロード、検証、準備、解析、初期化という5つのフェーズで構成されます。

ロード

「ロード "は、"クラスプラス機構 "の最初のプロセスであり、ロードフェーズでは、仮想マシンは、主に3つのことを完了します:

クラスによって定義されたバイナリ・バイト・ストリームを、その完全修飾名で取得します。

このバイト・ストリームで表される静的記憶構造を、メソッド領域の実行時データ構造に変換します。

このクラスを表すClassオブジェクトは、メソッド・エリアのこのデータへのアクセス・エントリとしてヒープに生成されます。

なぜなら、プログラマはシステムのクラス・ローダを使用してロードすることもできますし、独自のクラス・ローダを使用してロードすることもできるからです。このクラスローダーについては最後のセクションで詳しく説明します。ここで知っておく必要があるのは、クラス・ローダーが仮想マシンが行う必要のある上記の3つのことを行う、ということだけです!

検証

検証の主な役割は、ロードされるクラスが正しいことを確認することです。また、接続フェーズの最初のステップでもあります。単刀直入に言うと、ロードされた.classファイルは仮想マシンにとって有害であってはならないので、最初にテストと検証が行われます。

準備

準備フェーズでは、メモリの確保とクラス変数の初期値の設定が中心です。このメモリはメソッド領域に割り当てられます。

クラス変数はメモリを確保しますが、インスタンス変数は確保しません。インスタンス変数は主に、オブジェクトがインスタンス化されるときにJavaヒープに確保されます。

ここでいう初期値とは、データ型のデフォルト値のことで、コードの中で代入されることが示されている値のことではありません。

パースします。

解決フェーズは主に、仮想マシンが定数プール内の記号参照を直接参照に変換するプロセスです。解決動作は主に、クラスまたはインターフェイス、フィールド、クラス・メソッド、インターフェイス・メソッド、メソッド・タイプ、メソッド・ハンドル、コール・ポイント修飾子の 7 種類の記号参照に対して実行されます。

初期化

これはクラス・ロード・メカニズムにおける最後のステップであり、Javaプログラム・コードが実際に実行され始めるところです。クラス変数には、準備段階ですでに値が割り当てられています。初期化の段階で、プログラマは必要に応じて値を割り当てることができます。< clinit >この段階を一言で表すと、クラスのコンストラクタ()メソッドを実行するプロセスです。

初期化フェーズでは、JVMはクラスの静的変数(主にクラス変数)に正しい初期値を代入して、クラスを初期化する責任を負います。Javaでクラス変数に初期値を設定するには、2つの方法があります:

クラス変数の宣言とは、初期値を指定することです。

静的コードブロックを使って、クラス変数の初期値を指定します。

JVMの初期化ステップ

1.このクラスがロードおよび接続されていない場合、プログラムは最初にクラスをロードおよび接続します。

2.クラスの直接の親が初期化されていない場合は、直接の親を最初に初期化します。

3.クラス内に初期化文がある場合、システムはそれらを順番に実行します。

クラスの初期化のタイミング:クラスのアクティブな使用によってクラスの初期化が行われる場合のみ、クラスのアクティブな使用には以下の6つが含まれます:

  • クラスのインスタンスの作成、つまり新しいやり方
  • クラスまたはインターフェースの静的変数へのアクセス、またはその静的変数への値の代入。
  • クラスの静的メソッドの呼び出し
  • リフレクション
  • クラスのサブクラスを初期化すると、その親クラスも次のようになります。
  • Java VMの起動時にスタートアップ・クラスとしてマークされるクラスを初期化し、java.exeコマンドを使ってメイン・クラスの1つを実行するだけです。

クラス・ローダー

javaシステムに付属する3つのクラス・ローダー

クラス・ローダの階層構造

クラスをロードする3つの方法

main()メソッドを含むメイン・クラスは、アプリケーションがコマンド・ラインから起動されると、JVMによって最初にロードされます。

Class.forName()メソッドによるダイナミック・ロードでは、デフォルトで初期化ブロックが実行されますが、Class.forName(name,initialize,loader)のinitialzeでは、初期化ブロックを実行するかどうかを指定できます。

ClassLoader.loadClass()メソッドによって動的にロードされ、初期化ブロックは実行されません。

2親委譲の原則

クラス・ローダがクラス・ローディング・タスクを受け取ると、まずそれを親クラス・ローダに渡して完了させます。そのため、最終的にローディング・タスクはトップレベルのスターター・クラス・ローダに渡され、親クラス・ローダがローディング・タスクを完了できない場合にのみ、ローディング・タスクを実行しようとします。

2親委譲を使用する利点の一つは、例えば、クラスをロードするローダに関係なく、rt.jarパッケージにあるクラスjava.lang.Objectをロードし、最終的に別のクラスローダの使用は、最終的に同じObjectオブジェクトであることを保証するロードする起動クラスローダの先頭に委譲されていることです。2親委譲の原則は、このように集約されます:

  • 親クラスがすでにロードされている場合、子クラスを再度ロードする必要はありません。
  • あなたがこのアプローチを使用しない場合は、問題を統一するために、各クラスローダの基本クラスへの良い解決策は、、名前ユーザーが任意にコアAPIをロードするクラスローダを定義するには、関連する落とし穴をもたらすでしょう。

カスタム・クラス・ローダー

クラス・ローダーを定義するには、主に2つの方法があります。

ClassLoader を継承し、findClass() メソッドをオーバーライドします。

クラス・ローダーを継承し、loadClass() メソッドをオーバーライドします。 最初のメソッドは、クラスローダをカスタマイズして二親委譲モデルを最大限に遵守するために通常推奨されます。

ランタイム・データ領域

The Java Virtual Machine defines various run-time data areas that are used during execution of a program. Some of these data areas are created on Java Virtual Machine start-up and are destroyed only when the Java Virtual Machine exits. Other data areas are per thread. Per-thread data areas are created when a thread is created and destroyed when the thread exits.
Java仮想マシンは、プログラム実行中に使用されるさまざまなランタイム・データ領域を定義する。 これらのデータ領域のいくつかは、Java仮想マシンが起動するときに作成され、Java仮想マシンが終了するときにのみ破棄される。 他のデータ領域はスレッドごとにある。 各スレッドのデータ領域は、スレッドが作成されるときに作成され、各データ領域はスレッドが終了するときに破棄される。

メソッド領域

Java仮想マシンには、すべてのJava仮想マシンスレッド間で共有されるメソッド領域があります。メソッド領域は、通常の言語におけるコンパイル済みコードの格納領域や、オペレーティング・システムのプロシージャーにおける「テキスト」セグメントに似ています。実行時定数のプール、フィールドとメソッドのデータ、メソッドとコンストラクタのコード、クラスとインスタンスの初期化とインターフェースの初期化のための特別なメソッドなど、各クラスの構造が格納されます。

メソッド領域は仮想マシンの起動時に作成されます。メソッド領域は論理的にはヒープの一部ですが、単純な実装では、それらをゴミ箱に集めない、またはコンパクトにすることができます。この仕様では、メソッド領域の場所や、コンパイルされたコードを管理するためのポリシーは指定されていません。メソッド領域は固定サイズにすることもできますし、より大きなメソッド領域が必要なければ、計算の必要に応じて拡大縮小することもできます。メソッド領域のメモリは連続している必要はありません。

Java 仮想マシンの実装では、プログラマやユーザにメソッド領域の初期サイズや、メソッド領域のサイズが可変の場合は、メソッド領域の最大サイズと最小サイズの制御を提供できます。

メソッド領域には以下の例外条件があります:

  • 割り当て要求を満たすためにメソッド領域のメモリを提供できない場合、Java 仮想マシンは OutOfMemoryError をスローします。

この時点では、次のように、最初のステップと次の理解の組み合わせの2番目のステップにクラスファイルからロードします。

jdk8のメソッド領域はMetaspace、jdk6または7のメソッド領域はPerm spaceです。

ランタイム定数領域

はい 各クラスまたは各インターフェイスの constant_pool の実行時表現は、テーブル・クラス・ファイルにあります。これには、コンパイル時に既知の数値リテラルから、実行時に解決しなければならないメソッドやフィールド参照まで、いくつかの定数が含まれています。実行時定数プールは、通常のプログラミング言語のシンボル・テーブルと同様に機能しますが、一般的なシンボル・テーブルよりも大きな範囲のデータが含まれます。

実行時定数の各プールは、Java仮想マシンのメソッド領域から割り当てられます。Java 仮想マシンがクラスまたはインタフェースを作成すると、そのクラスまたはインタフェースの実行時定数のプールが構築されます。

以下の例外条件は、クラスまたはインターフェースのランタイム定数プールの構築に関するものです:

  • クラスまたはインターフェイスを作成するとき、実行時定数プールの構築で Java 仮想マシンのメソッド領域で利用可能なメモリよりも多くのメモリが必要になると、Java 仮想マシンは OutOfMemoryError をスローします。

プログラム・カウンタ

jvmプロセスは、実行中に複数のスレッドを持っていることを認識しており、スレッドの内容は、実行する権利を持つことができますにCPUスケジューリングに基づいています。

ある場所まで実行中のスレッドAが突然CPUの実行権を失い、スレッドBに切り替わり、その後再びスレッドAがCPUの実行権を得た場合、どうすれば実行を継続できるでしょうか?そのためには、スレッドの実行位置を記録する変数をスレッド内に保持する必要があります。

プログラムカウンタは、Java仮想マシンのマルチスレッドは、スレッドの回転スイッチングを介しているため、小さなメモリ空間を占有し、プロセッサの実行時間の割り当ては、いつでも、方法を達成するために、プロセッサは、命令のスレッドを実行しますので、スレッドのスイッチングに正しい実行位置に復元することができます、各スレッドは、独立したプログラムカウンタを持っている必要があります。

ネイティブでない場合、この pc レジスタには、現在実行されている Java Virtual Machine 命令のアドレスが格納されます。スレッドによって現在実行されているメソッドがネイティブの場合、Java Virtual Machine pc レジスタの値は未定義です。

スタック

Java仮想マシンには、すべてのJava仮想マシン・スレッド間で共有されるヒープがあります。ヒープとは、すべてのクラス・インスタンスと配列にメモリが割り当てられる実行時データ領域のことです。

ヒープは仮想マシンの起動時に作成されます。Java仮想マシンは、特定のタイプの自動ストレージ管理システムを想定しておらず、実装者のシステム要件に基づいてストレージ管理手法を選択できます。ヒープのサイズは固定することも、計算要件に応じて拡張することも、大きなヒープが必要なければ縮小することもできます。ヒープのメモリは連続している必要はありません。

Java仮想マシンの実装では、ヒープの初期サイズと、ヒープを動的に拡張または縮小できる場合は、ヒープの最大サイズと最小サイズの制御をプログラマまたはユーザーに提供できます。

ヒープには次のような例外があります:

  • 計算が自動ストレージ管理システムが提供できる以上のヒープを必要とする場合、Java 仮想マシンは OutOfMemoryError をスローします。

仮想マシン・スタック

Java仮想マシンスレッドはすべて、そのスレッドと同時に作成されるプライベートを持っています。Java仮想マシンスタックは、フレームのスタックを格納します:それはローカル変数と部分的な結果を保持し、メソッドの呼び出しと戻り値に作用します。Java仮想マシン・スタックは、フレームのプッシュとポップを除いて直接操作されることがないため、フレームをヒープに割り当てることができます。

この仕様では、Java仮想マシン・スタックが固定サイズを持つことも、計算要件に基づいて動的に拡大・縮小することもできます。Java VMスタックが固定サイズの場合、各Java VMスタックのサイズは、作成時に独立して選択できます。

Java仮想マシンの実装は、Java仮想マシン・スタックの初期サイズの制御をプログラマーやユーザーに提供することができ、Java仮想マシン・スタックを動的に拡大または縮小する場合には、最大サイズと最小サイズを制御することができます。

以下の例外条件は、Java仮想マシン・スタックに関連しています:

  • スレッド内の計算で、許可される Java 仮想マシンよりも大きなスタックが必要な場合、Java 仮想マシンは StackOverflowError をスローします。
  • Java VMスタックを動的に拡張することが可能で、それを試みても、拡張を有効にするのに十分なメモリが提供できない場合、または新しいスレッド用の初期Java VMスタックを作成するのに十分なメモリがない場合、Java VMマシンは、OutOfMemoryErrorをスローするために使用できます。

Java仮想マシン・スタックはスレッド・プライベートであり、スレッドと同じライフサイクルを持ちます。仮想マシン・スタックは、Java メソッド実行のメモリー・モデルを記述します。スタック・フレームは、Java 仮想マシンによるメソッドの呼び出しと実行をサポートするために使用されるデータ構造で、仮想マシン・スタックのスタック要素です。各メソッドは、ローカル変数テーブル、オペランド・スタック、ダイナミック・リンク、メソッド終了、および実行中のその他の情報を格納するためのスタック・フレームを作成します。

呼び出しから実行処理が完了するまでの各メソッドは、スタックに入る処理からスタックから出る処理までの仮想スタック内のスタック・フレームに対応します。JVM内部では、スタック・フレームの操作は、アウトとインの2種類しかありません。スレッドによって実行されているメソッドは現在のスレッドメソッドと呼ばれ、メソッドのスタックフレームは現在のフレームと呼ばれ、実行エンジンは実行時に現在のスタックフレームに対してのみ有効です。

ローカル変数の表

ローカル変数テーブル(Local Variable Table)は、メソッド内部で定義されるメソッド・パラメータやローカル変数の変数値格納領域の集合です。メソッドに割り当てられるローカル変数テーブルの最大容量は、JavaプログラムをClassファイルにコンパイルする際に、メソッドのcode属性のmax_localsデータ項目で決定されます。

ローカル変数には、クラス・メンバ変数のような「準備段階」がありません。クラス変数には、システムから初期値が与えられる準備フェーズと、プログラマが定義した初期値が与えられる初期化フェーズの 2 回、初期値が割り当てられます。したがって、初期化フェーズでプログラマがクラス変数に値を割り当てなくても、クラス変数には定義された初期値が残っているので問題ありません。しかし、ローカル変数は同じではありません、ローカル変数が定義されているが、初期値が割り当てられていない場合は使用することはできません、そのようなデフォルト値として0に整数変数、falseにブール変数のデフォルトなど、どのような場合にJavaがあると思わないでください。

オペランド・スタック

オペランドスタックは、しばしばオペレーションスタックとも呼ばれ、バックイン・ファーストアウトのスタックです。

メソッドの実行が開始されたばかりのときは、メソッドのオペランドスタックは空で、メソッドの実行中に、オペランドスタックの内容を書き出したり、取り出したりするための様々なバイトコード命令があります。例えば、オペランドスタックを経由して算術演算が行われたり、他のメソッドを呼び出す際にオペランドスタックを経由してパラメータが渡されたりします。例えば、整数の加算を行うバイトコード命令 iadd は、オペランドスタックの最上位に最も近い 2 つの要素に 2 つの int 値が格納された状態で実行され、命令が実行されると 2 つの int 値がスタック上で加算され、加算結果がスタックに加算されます。

ダイナミック・リンク

クラス・ファイルの定数プールには多数のシンボリック参照が含まれており、バイトコードのメソッド呼び出し命令は、定数プール内のメソッドへのシンボリック参照を引数として取ります。これらのシンボリック参照の一部は、クラスのロード段階または最初に使用されるときに直接参照に変換されます。これは動的連結と呼ばれます。

JavaコードがJavacによってコンパイルされるとき、CやC++のように「連結」されるのではなく、仮想マシンがクラス・ファイルをロードするときに動的に連結されます。言い換えると、各メソッドやフィールドの最終的なメモリ・レイアウトはクラス・ファイルには格納されていないため、これらのフィールドやメソッドへのシンボリック参照は、実行時に変換して実際のメモリ・ポピュレーション・アドレスを取得しない限り、仮想マシンでは使用できません。仮想マシンの実行時には、定数プールから対応するシンボリック参照を取得し、クラス作成時または実行時にそれらを解決して特定のメモリ・アドレスに変換する必要があります。

Math math=new Math();
math.compute();//インスタンス・メソッドcompute()を呼び出す

例として、コードの上記の2行を取り、動的な接続を説明します:math.compute()コールcompute()シンボルと呼ばれる、あなたは定数プールにcompute()このシンボルを通過する必要がありますシンボリック参照の対応するメソッドを見つけるには、ランタイムは、メモリアドレスのバイトコード命令のメソッドを見つけるためにシンボリック参照を介してされます。

メソッドのリターン・アドレスは

メソッドの実行が開始されると、メソッドを終了する方法は 2 つしかありません。最初の方法は、エンジンによって遭遇したメソッドのいずれかによって返されたバイトコード命令を実行することで、その時点で戻り値が高レベルのメソッド呼び出し元に渡される可能性があります。メソッドを終了するこの方法は、通常の完了終了と呼ばれます。例外完了終了で終了するメソッドは、その上位レベルの呼び出し元に戻り値を生成しません。

使用される終了メソッドに関係なく、メソッドが終了した後、プログラムが実行を続行するためにメソッドが呼び出された場所に戻る必要があり、メソッドは戻るときにスタック・フレームにいくつかの情報を保存する必要があるかもしれません。一般に、メソッドが正常に終了する場合、呼び出し元の PC カウンタの値をリターン・アドレスとして使用でき、スタック・フレームはこのカウンタ値を保持する可能性が高いです。対照的に、メソッドが異常終了する場合、リターン・アドレスは例外ハンドラ・テーブルによって決定され、この情報は通常スタック・フレームには保存されません。メソッドの終了処理は、実際には現在のスタック・フレームをスタックから取り出すことと等価であるため、終了時に実行可能な操作としては、上位メソッドのローカル変数テーブルとオペランド・スタックを復元する、呼び出し元のスタック・フレームのオペランド・スタックに戻り値を押し込む、メソッド呼び出し命令に続く命令を指すように pc カウンタの値を調整する、などが考えられます。

ネイティブ・メソッド・スタック

Java仮想マシンの実装では、従来のスタックを使用してネイティブ・メソッドをサポートすることができます。インタプリタの実装は、ネイティブメソッドスタックを使用するためにJava仮想マシンの命令セットを解釈するためにC言語などの言語を使用することもできます。メソッドをロードできず、それ自体が従来のスタックに依存しないJava仮想マシン実装は、ネイティブメソッドスタックを提供する必要はありません。提供される場合、ネイティブ・メソッド・スタックは通常、各スレッドの作成時に割り当てられます。

  • Java VM は、スレッド内の計算が許容されるよりも大きなネイティブ・メソッド・スタックを必要とする場合、StackOverflowError をスローします。
  • ネイティブ・メソッド・スタックを動的に拡張することができ、ネイティブ・メソッド・スタックの拡張を試みても、十分なメモリーを提供できない場合、または新しいスレッドの初期ネイティブ・メソッド・スタックを作成するのに十分なメモリーを提供できない場合、Java VMはOutOfMemoryErrorをスローします。
Read next

数千行のSDKコードを最適化する方法

最近、プラグインモードと一括インポート機能の使用を共有するために、xgplayerのソースコードを見始め、SDKファイルの最適化コードの数千行のアプリケーションから引き出すことができます。 ソースコードを見て、最も重要なことは、デバッグできるようにすることです、プレーヤーのソースコードは、ブラウザ側で実行されているので、直接デバッグのためにブラウザを使用することができ、私は説明する記事をフォローアップするデバッグする方法のソースコード上のノード。 ここで設定...

Apr 21, 2020 · 5 min read

異形のES6エッセイ

Apr 21, 2020 · 6 min read

天地乾坤借用法

Apr 20, 2020 · 9 min read

CSS基礎の復習 - 相対単位

Apr 20, 2020 · 2 min read

私の中のGit「中級者

Apr 20, 2020 · 2 min read