JVMクラスローディング機構 - クラスのライフサイクル
クラスローディング機構とは何ですか?
クラス(バイトコード)を記述する、仮想マシンのデータファイルは、最終的にはJava型(のjava.lang.Classオブジェクト)を直接仮想マシンで使用することができる、メモリ、及びデータ検証、製造、解像度、およびクラス初期にロードされますこれは、クラスローディング機構のJava仮想マシンです。 - 「Java仮想マシンの深い理解」
クラスのライフサイクル
クラスメモリは時代遅れアンロードされるまで、最初からメモリにロードされ、ライフサイクルは、クラス含む用いローディング、検証、製造、解像度、初期化、およびアンロードを請求項7つのプロセスを解析、検証、製造、三つのプロセスをまとめてリンクと呼ばれます。
このプロセスは、順番に7を開始しますが、実際に、彼らは通常、クロスカット動作と混合され、実行するためのプロセスを開始することができ、前のプロセスの次の終了まで待つ必要はありません。
Javaでは、クラスのロードとリンク処理は、プログラムの実行中に完了されます。また、Javaのできる動的に拡張可能な言語機能は、この機能が実装され、運転中に動的リンク動的ロードに依存しています。
次は、次のクラスのライフサイクル全体についての詳細を学びます
装填(ローディング)
ロード・フェーズでは、仮想マシンは、3つのことを完了するために:
-
クラスの完全修飾名で定義されたバイナリバイトストリームを取得するには。
クラスからバイナリバイトストリームファイルを取得するだけでなく、どのような領域から入手できますか?
从ZIP或jar包中读取 从网络中获取 运行时计算生成(Java动态代理技术) 由其他文件生成(由JSP文件生成对应的Class类) 从数据库中读取 复制代码
-
プラスバイナリバイトストリームは、(所望の仮想マシンによるストレージフォーマット)方式エリアに格納されます。
-
メモリ内の発生は、このクラスの各種データ入力領域にアクセスするための方法として、このクラスのjava.lang.Classオブジェクトを表します。Classオブジェクトは、メソッドエリア(HotSpot仮想マシン)に保存されている、かなり特殊です
第二に、確認してください
検証の目的は、クラスに含まれる情報のバイトストリームファイルが自分自身の安全を危うくしません、現在の仮想マシンと仮想マシンの要件を満たしていることを確認することです。一般的には二つの側面が含まれています。
- フォーマットセマンティクスがチェックします。
例如: 1.是否以0xCAFEBASE开头 2.主、次版本号是否在当前虚拟机处理范围内 复制代码
- コードチェックロジック
第三に、準備
準備フェーズは、正式に静的変数メモリとして割り当てられ、方法エリアにメモリを割り当てるために、これらの静的変数の初期値を設定します。
注意:
-
準備フェーズでは、JVMは、静的変数(静的変形)のためにメモリを割り当てる、オブジェクトはJavaヒープにインスタンス化されるとき、インスタンス変数がオブジェクトと一緒に割り当てられる、インスタンス変数を含んでいません。
准备阶段,只会为value分配内存,不会为name分配内存 public static int value = 123; private String name = "Tom"; 复制代码
-
初期値を設定します。
- 修飾された静的変数(最終ではない):ゼロまたはヌルの値
準備フェーズ、任意のJavaメソッドを実行しない、命令123にプログラムを割り当てられた値は、コンパイルされたクラスのコンストラクタメソッドに格納され、初期化段階で実行されている、準備フェーズは、ゼロの値を設定します。
准备阶段,会设置零值
public static int value = 123;
复制代码
- 定数の静的な最終修正:実際の値
常量,准备阶段会设置实际值123
public static final int value = 123;
复制代码
注:静的最終的な改変基本データ型またはString属性一定値コンパイルのjavacで生成、直接準備フェーズをクラスローディングフィールド一定値の値を割り当てます。これは、定数プールに入れられ、コンパイル時に結果として理解することができます。したがって、クラスは、クラスA、Bのstatic finalフィールド(基本データ型または文字列)が呼び出されたときに、負荷クラスBをトリガしません
第四に、解決
解決フェーズは、プロセス中に直接参照された仮想マシンの参照記号定数プールで主参照クラスまたはインタフェース、フィールド、クラスメソッド、4つのシンボルインタフェース方法に分析動作を(いくつかのケースで開始することができる初期化フェーズの後)以下のため、それぞれの定数プールに対応するCONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info
定数タイプの四種類。
1、クラスまたはインタフェースを解析:異なる分析を実行するように所望の直接参照が配列型、または参照オブジェクトの通常のタイプに変換する決定。
図2に示すように、フィールドに解決:フィールドが解析されると、なります最初のこの種の中で検索]フィールドの端を見つけるために、そうならば単純名とフィールド記述子は、目標に一致している含まれている、そうでない場合、それは継承に従います再帰検索は、各インターフェイスは、クラスとその親インタフェースによって実装、まだ、検索が終了するまで再帰的に、親クラスの継承を検索する方法。
3、クラス分析方法:分析法クラスフィールドと検索を解析するステップは、唯一の多クラスまたは方法ステップにおける界面の決意、及びクラスマッチング検索方法では、検索は、最初の親クラスであり、その後、検索インタフェース。
図4は、インタフェース分析方法:方法に基づく分析工程等が挙げられるが、インターフェースは、親を持たない、およびライン上のため、唯一の親インターフェイス再帰探索方向。
次の例を見てください:
class Super{
public static int m = 11;
static{
System.out.println("执行了super类静态语句块");
}
}
class Father extends Super{
public static int m = 33;
static{
System.out.println("执行了父类静态语句块");
}
}
class Child extends Father{
static{
System.out.println("执行了子类静态语句块");
}
}
public class StaticTest{
public static void main(String[] args){
System.out.println(Child.m);
}
}
复制代码
出力:
执行了super类静态语句块
执行了父类静态语句块
33
复制代码
なぜ静的ブロックのサブクラスを実行しませんか?
static块是在初始化阶段执行的,而static变量发生在静态解析阶段,也即是初始化之前,此时已经将字段的符号引用转化为了内存引用,也便将它与对应的类关联在了一起,由于在子类中没有查找到与m相匹配的字段,那么m便不会与子类关联在一起,因此并不会触发子类的初始化。
复制代码
ラインがmに対する父クラス定義をコメントした場合、次のように同様に、出力結果は、次のとおり
执行了super类静态语句块
11
复制代码
V.の初期化
最後のステップは、実際にクラス定義でJavaコードを実行するために始めた、このステージに、クラスのロード処理を初期化することです。準備フェーズでは、クラス変数は、システム要件に一度初期値を割り当てられていたが、初期化フェーズにおいて、プログラマ主観的計画によって指定された手順に従ってクラス変数および他のリソースを初期化すること、または別の観点から表すことができます。初期化フェーズは、クラスコンストラクタ実行される<clinit>()
プロセスアプローチを。
ここでは簡単な手順ルール()メソッド:
-
<clinit>()
この方法は、コンパイラ自動的に収集クラスの文によって収集されたブロックの合併順次コンパイラのすべてのクラス変数と静的ステートメントのステートメントの割り当て操作で、彼らは意思決定のソースファイルに表示される順序、静的文ブロックステートメントのブロック内で定義された静的変数へのアクセスのみを前に、それの後に変数で定義され、静的ステートメントの前に割り当てることができますが、アクセスすることはできません。 -
<clinit>()
例のコンストラクタのメソッド<init>()
メソッド(クラスのコンストラクタ)異なるが、それが明示的に親クラスのコンストラクタ、呼び出さないサブクラスであることを確認するために仮想マシンを<clinit>()
メソッドを実行する前に、親クラスの<clinit>()
メソッドが完了しています。したがって、第一の仮想マシンで実行される<clinit>()
クラスメソッドは、java.lang.Objectのなければなりません。 -
<clinit>()
クラスまたはインタフェースのための方法は、コンパイラーは、クラスに対して生成されないことがあり、クラスが文の静的なブロックではない場合、クラス変数のない割り当てが存在しない、必要ではない<clinit>()
方法。 -
インターフェイスブロック静的ステートメントは使用できませんが、割り当てを初期化クラス変数(最終的な静的)は依然として存在するので、同じクラスインターフェースを生成
<clinit>()
する方法を。しかし、インタフェースは、クラスとは異なる:インタフェースの実装は、<clinit>()
親インタフェース実行する必要のない<clinit>()
方法を、インターフェイスで定義された親変数が使用された場合にのみ、親インターフェイスを初期化します。ときに初期化さらに、インタフェースを実装するインタフェースの実装クラスが同じではない<clinit>()
方法。 -
クラス確保するための仮想機会
<clinit>()
の方法が適切にロックされ、マルチスレッド環境で同期されている複数のスレッドがクラスを初期化する場合は、その後、唯一のこのタイプを実行するために、1つのスレッドがあるだろう<clinit>()
方法を、他のスレッドが待機する必要がブロックされています、までアクティブスレッド実行<clinit>()
方法が完了する。クラスがいる場合<clinit>()
、長い動作時間のかかるプロセスを持って、それがこの閉塞の実用化にブロックされた複数のスレッドになることがあり、多くの場合、非常に微妙です。
単純な例は、より明確に上記の規則を説明するために以下に与えられます。
class Father{
public static int a = 1;
static{
a = 2;
}
}
class Child extends Father{
public static int b = a;
}
public class ClinitTest{
public static void main(String[] args){
System.out.println(Child.b);
}
}
复制代码
出力:
2
复制代码
後は、のコードの外観の手順を実行します。
- 準備フェーズ:及びbはデフォルト値0が割り当てられるように、初期値を設定するクラス変数およびクラス変数にメモリを割り当てます
- 初期段階:そして呼び出し
<clinit>()
彼らはプログラムで指定されたメソッドに値を割り当てられています我们调用Child.b时,触发Child的<clinit>()方法,根据规则2,在此之前,要先执行完其父类Father的<clinit>()方法, 又根据规则1,在执行<clinit>()方法时,需要按static语句或static变量赋值操作等在代码中出现的顺序来执行相关的 static语句,因此当触发执行Father的<clinit>()方法时,会先将a赋值为1,再执行static语句块中语句,将a赋值为2, 而后再执行Child类的<clinit>()方法,这样便会将b的赋值为2. 如果我们颠倒一下Father类中“public static int a = 1;”语句和“static语句块”的顺序,程序执行后,则会打印出1。 很明显是根据规则1,执行Father的<clinit>()方法时,根据顺序先执行了static语句块中的内容, 后执行了“public static int a = 1;”语句。 另外,在颠倒二者的顺序之后,如果在static语句块中对a进行访问(比如将a赋给某个变量), 在编译时将会报错,因为根据规则1,它只能对a进行赋值,而不能访问。 复制代码
第六に、使用
引用文献にフェーズがアクティブおよびパッシブ含ん使用し、イニシアチブは、クラスの初期化、およびパッシブリファレンスは、クラスの初期化が発生しない原因になります引用。
アクティブリファレンス
JVMは、厳格なルールを持っている場合にのみ場合は、以下の5つの条件、つまり、アクティブリファレンスは、初期化がクラスをトリガーする、加えて、クラスメソッドへのすべての参照の初期化がトリガされません!
-
クラスが初期化されていない場合は、新しいgetstatic、putstaticが発生した、ときに、これらの4つのinvokestaticバイトコード命令、その後すぐに初期化を行いました。実際には、3例:ときにクラスの新しいインスタンス、または同様に、(それらが定数プールに詰めていたので、最終的な変性静的フィールドを含まない)のクラスに静的フィールドを読み取るときに静的メソッドを実行する場合など。
-
クラスが初期化されていない場合、というクラスを反映するjava.lang.reflectは。*アプローチを使用する場合は、すぐにも。
-
彼の父が初期化されていない場合、クラスを初期化し、父親に行くために、それを初期化します。
-
JVMが起動すると、ユーザーが実行されるメインクラス(静的な無効メイン(文字列[] argsを含む)、そのカテゴリ)を指定する必要があり、JVMは、クラスを初期化するために行くだろう。
-
Class.forNameの(文字クラス名)を用いて、クラスをロードする時、初期化動作も行われます。
- 注:はloadClass(クラス名の文字列)のクラスローダと、この方法は、ロードしたクラスをコンパイルし、その実装が初期化されないであろう。
パッシブリファレンス
- 静的フィールドは、親クラスを参照し、サブクラスの初期化を発生させることなく、親クラスを初期化するためにつながります。
- クラスの配列を定義して、それがクラスの初期化が発生することはありません。
- クラスへの静的な最終的な一定の基準、(静的な修正場合、またはクラス初期化の原因となる)クラス初期化を引き起こさないであろう。
受動的基準サンプルコード:
class InitClass{
static {
System.out.println("初始化InitClass");
}
public static String a = null;
public final static String b = "b";
public static void method(){}
}
class SubInitClass extends InitClass{
static {
System.out.println("初始化SubInitClass");
}
}
public class Test4 {
public static void main(String[] args) throws Exception{
// String a = SubInitClass.a;// 引用父类的静态字段,只会引起父类初始化,而不会引起子类的初始化
// String b = InitClass.b;// 使用类的final常量不会引起类的初始化
SubInitClass[] sc = new SubInitClass[10];// 定义类数组不会引起类的初始化
}
}
复制代码
使用段階が完了すると、Javaクラスがアンロード段階に入りました。
七、アンインストール
クラスの使用が終了した後、以下の条件場合は、真のクラスがアンロードされています。
- クラスのすべてのインスタンスが回復してきた、それは、クラスのインスタンスが存在しないJavaヒープです。
- ロードクラスのクラスローダは、回収されました。
- クラスオブジェクトに対応するのjava.lang.Classはどこでもクラスにアクセスする方法で反映されていない、どこにも参照されません。
上記3つの条件が満たされた場合には、ガベージコレクションのメソッドの地区は、アンインストールプロセスが実際に種類の方法クラス情報にエリアをクリアすると、JVMクラスがアンロードされます、ライフサイクル全体でのJavaクラスは終わりました。
概要
全体クラスローディングプロセスは、外部の関与からのクラスローダを定義することができ、ユーザアプリケーションに加えて、仮想マシン内の残りのすべてのアクションが完全にローディングステージと制御によって支配します。クラスで定義されている(また、バイトコード)は、Javaを実行するための初期化プログラムコードを始めるが、これに限られるコード実行の始まりに過ぎないために、<clinit>()
方法。クラスのロードプロセスは、主に実際のロードが完了した後にのみ開始しました(正確には、それはバイナリバイトストリームのクラスでなければなりません)、実際に操作バイトコードを実行し、仮想マシンのメモリにロードされ、クラスファイルです。