JVMの研究ノート - クラスローディング機構

  クラス仮想マシンは、メモリにファイルをロードし、データは、JVMクラスローディング機構は、仮想マシンとして使用することができるJava型を形成し、クラスファイル、構文解析および変換の初期化を確認したクラスファイルに加えてディスクファイルに格納され、他の形態で存在することができ、具体的にはバイナリバイトストリームの一連のことをいいます。

1、ライフサイクル

  検証、製造および分析部が都市に接続されローディング、検証、製造、解像度、初期化、およびアンロードの使用:メモリからクラスはメモリ、ライフサイクル全体をアンロードするために仮想マシンにロードされます。次に、負荷、検証、製造、初期化およびアンロードは、(ちょうど始まり)を開始するためにする必要がある解析が初期化後に動的バインディングをサポートするために使用することができる始めます。

2、クラスローディング手順

2.1ロード

   フォーマット領域の処理で仮想マシンを格納するために必要なバイトのバイナリストリーム、仮想マシン自体によって定義されるプロセスゾーン記憶フォーマット、およびメモリ内でインスタンスのクラスオブジェクト(に従ってHotSpot仮想マシンクラスオブジェクトはに配置されていない外部の仮想マシンしかし、ヒープ領域の方法で)。ロード・フェーズでは、仮想マシンは、3つのことを行う必要があります。

このようなバイナリバイトストリームを得るために図1に示すように、クラスの完全修飾名によって定義されます

2、ランタイムデータ構造領域メソッドにバイトストリームで表されるこれらの静的ストレージ構造

図3に示すように、このクラスの各種データ入力領域にアクセスするための方法として、このクラスのメモリのjava.lang.Classオブジェクトに生成する代表。

  非アレイベースクラスローディングステージバイナリバイトストリームを取得する動作が、完了したブートストラップクラスローダシステムを提供するために使用することができる完成されたユーザクラスローダによってカスタマイズすることができます。それ自体で配列クラスは、クラスローダを作成しないためには、Java仮想マシンによって直接作成されますが、クラスローダによって配列要素の型またはクラスが作成します。配列クラスの作成プロセスは、これらのルールに従う必要があります:

配列が参照型、クラスオブジェクトを取得するコンポーネントクラスの完全修飾名を取得する必要がある場合に1、コンポーネントタイプ(配列ディメンションを除く。)、アレイは、コンポーネント・タイプ・クラス・ローダのクラスの名前空間にロードされますこれは、(2次元配列?)が同定されます。

コンポーネントタイプが配列参照型でない場合は2、JVMは、アレイがブートクラスローダに関連付けられているマークします。

視認性のタイプと一致3、可視データタイプとその構成要素(同じ?)コンポーネントタイプが参照タイプでない場合は、配列クラスの可視性は、公的にデフォルト設定されます。

2.2検証

  自分自身の安全を危うくしません、現在の仮想マシンと仮想マシンの要件を満たすために、ファイルに含まれる情報のクラスのバイトストリームを|検証の目的は、確実にするためです。次の4つの段階があります。

1、ファイル形式の検証:

  入力バイトストリームが解析され、そのようなテストが正しい4バイト前にマジックナンバーとして、正しい地域法に格納することができることを確実にするために、クラス・ファイル・フォーマットに準拠してバイトストリームを検証します。領域の構造に基づいて、検証方法の後ろのエリアにバイトストリームを格納するための検証方法によって後。

2、メタデータのテスト:

  Java言語仕様の遵守を確保するためのメタデータ情報などセマンティックチェック。例えば、クラスは、最終的な修飾クラスを継承するかどうかをテストします。

3、バイトコード検証:

  方法は、試験体に基づいており、プログラムのセマンティクスを決定するために、データ・フローと制御フロー解析は、論理的な正当なものです。例えば、型変換は、オブジェクトのデータ・タイプに割り当てることができない何の継承関係はありません。

4、符号検証:

  解決フェーズでは、ときに、仮想マシンは、シンボル参照の検証をトリガする、直接参照シンボル参照に変換されます。シンボル参照情報は、このようなクラスは、対応する完全修飾名文字列、符号クラス、メソッド、およびフィールドに従って求めることができることを確認するために、マッチングチェックの(定数プール、様々なシンボルを参照)ローカル以外に基づいて検証されます現在のクラスにアクセスできるかどうか。

2.3準備

  (オブジェクトはヒープ上に割り当てられたオブジェクトとしてインスタンス化されるとき、インスタンス変数)を準備フェーズのためにメモリを割り当てることは、配布の方法で使用されるメモリ領域に、クラス変数および初期位相値が設定されています。クラス変数が一定である場合、すなわち、修飾された最終的な静的クラスと準備段階における変数とは、割り当てられるような準備段階123で次の値として、一定の値に初期化されます。

パブリック 静的 最終 int型値= 123。

他のクラス変数はゼロ値に初期化されています。次の変数の値は、準備フェーズの後に0であり、クラスのコンストラクタに割り当てられている<clinit>()123から初期段階であろう。

公共の 静的な int型の値= 123;

  ゼロ値の様々なタイプ:

データの種類 ゼロ値 データの種類 ゼロ値
CHAR '\ U0000' 浮く 0.0f
バイト (バイト)0 ダブル 0.0d
ショート (ショート)0 ブーリアン
int型 0 参照 ヌル
長いです 0L    

解析された2.4

  ステージを解析すると、直接参照を交換するプロセスのための基準の記号定数プールへの仮想マシンです。基準シンボル、クラスまたはインタフェース、フィールド、メソッドのクラス、インターフェイスメソッド、型メソッド、メソッド・ハンドラの7種類は、クラスファイルの定数プールCONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info、CONSTANT_MethodType_info、CONSTANT_MethodHandler、CONSTANT_InvokeDynamic_info7に対応する、修飾子のポイントを呼び出し、あります定数タイプ。特定の直接参照がメモリ内の基準位置(メモリオブジェクトの位置)を突き止めるために使用され、それは間接的に、ハンドル、および仮想マシンの実装を配置する宛先オフセットポインタに直接、またはメモリレイアウト相対できます関連。クラスローダ、またはシンボル参照のみになるまでは、前に解決するために使用されるとき、仮想マシンの仕様では、ロードすることは、ニーズに応じて解決することができ、時間分解相が発生する方法を指定しません。

  ときに、同じ参照符号多重解像度要求コマンドinvokedynamicのほか、第一の仮想マシンにキャッシュされた解析の結果(実行時定数プールレコード直接参照に、および解決一定の状態として識別される)を回避します解像度は一度だけ解析され成功したかどうか、解像度を繰り返します。命令は「動的呼び出しサイト修飾子」と呼ばれるリファレンスを対応する、動的言語をサポートするために使用されるようinvokedynamicの命令について、あなたはプログラムが実際にのみ解決のために、この命令に実行するまで待つ必要があります。

2.4.1分析クラスまたはインタフェース

  クラスはDである現在のコードを仮定し、我々が解析されたクラスやインタフェースCのN直接参照へのシンボリック参照を解決しませんしたい場合は、以下の手順が必要です。

1、Cは配列型でない場合、Nの仮想完全修飾名がクラスをロードするためにDクラスローダへ送信、ロード処理において、メタデータの検証、バイトコード検証するので、他のクラスがロードをトリガすることができますそのような親やロードなどのアクションは、実装されています。

2、Cが配列型であり、要素タイプはルール1ロード配列要素の種類に応じて、オブジェクトの配列であり、仮想マシンおよび要素を表すのこのアレイの寸法によってオブジェクトの配列を生成した場合。

図3は、上記工程Cの完了後に、仮想マシンが実際に有効なクラスまたはインタフェース、シンボリック参照のその後の検証、それらはC.へのアクセス権を持っているかどうかを確認Dとなっています

2.4.2フィールドの解析

  フィールドを解析することは、シンボル参照を解析されたシンボリッククラスまたはインタフェース参照フィールドが属する解決する必要があり、またはインタフェースが想定されていないCによって表されるクラスに属し、その後のステップを解決正常に行います:

C自体は、単純なフィールド名とフィールド記述子は、ターゲットと一致している含まれている場合は1、それは終わりを見つけるために、このフィールドへの直接参照を返します。

2.そうでない場合、Cは、インターフェイスを実装する場合、ボトムアップ再帰検索から継承に従って各インターフェイスとその親インターフェース、インターフェースは、単純なフィールド名とフィールド記述子の一致が含ま参照。

それ以外の場合は3、Cは親クラスの検索上向きから継承に従い、java.lang.Objectのではない場合。

4、それ以外の場合は、ルックアップが失敗します。

  見つかった場合は直接参照、アクセス権の確認を返します。同じ名前のフィールドもCインタフェースや親クラスに表示され、またはインタフェースや独自の親クラスの両方に表示された場合、コンパイラは文句を言うでしょう。

2.4.3クラスの分析方法

  分析方法クラスクラスメソッドは、クラスまたはインタフェースに関連するシンボリック参照を解決する必要があり、クラスまたはインタフェースがCクラスの解像度が成功を検索する方法で表されているものとします。

Cがインターフェイスである場合1は、例外がスローされます。

図2に示すように、この方法に戻り、探索を終了への直接参照がある場合、カテゴリCの単純な名前と記述マッチングであるかどうかを見つけます。

3、それ以外の場合は、親クラスの昼食クラスC再帰検索。

検索が成功した場合4.そうでない場合、インタフェースと、親インターフェースのクラスCの実装のリストに再帰検索は、クラスCは、抽象クラスであることを示し、例外がスローされjava.lang.AbstractMethodError。

5.それ以外の場合は、ルックアップが失敗します。

  見つかった場合は、アクセス許可を確認するために、直接参照を返します。

2.4.4インタフェース方法解決

  正常に解決場合クラスまたは参照シンボルが属するインターフェースメソッドを解析するために、クラスまたはインタフェースがC、検索インターフェース方法で表されているものとします。

Cクラスではなくインタフェースであれば1は、例外がスローされます。

図2に示すように、このメソッドの戻り値を直接参照がある場合にそうでない場合は、単純な名前及び記述子があるかどうかをCインタフェースに見えるが、一致している、端を見つけます。

3、それ以外の場合は、親インタフェース再帰的なインターフェイスに見えます。

4、それ以外の場合は、ルックアップが失敗します。

  すべてのインターフェイスメソッドが公開され、何のアクセスの問題はありません。

2.5初期化

  クラスのロードの前に、カスタム・ローダへの参加に加えて、その動作は完全に仮想マシンのアプリケーションと制御ローディングステージによって支配されます。本当に初期化フェーズで定義されたJavaコードのクラスを実行し始めました。仮想マシン仕様厳格なルールやクラスの唯一の5例は(ロード、検証、自然に前の準備)を初期化する必要があります。

クラスが初期化されていない場合は、このバイトコード命令は、初期化がトリガする必要がある場合に1は、新しい、getstatic、putstatic invokestaticまたは4に遭遇します。4つの命令の一般的なシナリオを生成することである。オブジェクトをインスタンス化する新しいキーワードを使用して、クラスの読み取りまたは静的フィールドのセット(即ち、結果の定数プールへの最終的な修飾コンパイラを除く)、クラス静的メソッド呼び出し。

場合2クラスが初期化されていない場合には、反射コールに基づく方法を用いてjava.lang.refectパッケージは、フリップフロップを初期化する必要があります。

親クラスの初期化が発見されていない場合3、クラスが初期化されるとき、あなたは親クラスの初期化をトリガーする必要があります。

仮想マシンが起動されたときに図4に示すように、ユーザが実行するメインクラス(そのクラスのmain()メソッドが含まれている)、このマスタークラスを初期化する仮想マシンを指定する必要があります。

動的言語サポートjdk1.7を使用する場合、分析の最終結果は、java.lang.invoke.MethodHandle例REF_getStatic場合5は、、、REF_putStaticは、方法は、初期化されていない対応するREF_invokeStatic、この方法ハンドルクラスを扱う、それがする必要がありますその初期化をトリガします。

  シーンのこれらの5種類に加えて、残りの受動的な参照は、初期化がトリガされません。たとえば、サブクラスを誘発しない静的フィールドの親クラスの初期化を呼び出すには:

パブリック クラススーパークラス{ 

    パブリック 静的 int型値= 12 

    静的{ 
        のSystem.out.println( "スーパークラスのinit" )。
    } 

} 

パブリック クラスのサブクラスは延びスーパークラス{ 

    静的{ 
        するSystem.out.println(「サブクラスINITを」)。
    } 

} 

パブリック クラスTest1を{ 

    公共 静的 ボイドメイン(文字列[]引数){ 
        のSystem.out.println(SubClass.value)。
    } 

}

  このコードは、出力のみ「スーパークラスのinit」とありませんが、「サブクラスのinit」を失うことになります。

  次のコードは、説明初期化をトリガしない操作後の出力「スーパークラスINIT」、ではないが、追加のコードが「[Lcom.yue.main.SuperClass」(com.yue.mainパッケージはスーパークラスと呼ばれるトリガ初期設定名)クラス。自動的に仮想マシンによって生成される、java.lang.Object上位の直接サブクラスは、ユーザコードの有効なクラス名ではなく、このクラスは、要素型がcom.yue.main.SuperClassの一次元配列で表します。

パブリック クラスTest1を{ 

    公共 静的 ボイドメイン(文字列[]引数){ 
        スーパークラス[] ARR = 新しいスーパークラス[2 ]。
    } 

}

  初期化フェーズは、クラスのコンストラクタ<clinit>()メソッド中に行われます。次の点に注意してください。

1、<clinit>()メソッドは、そのため、ソース・コード・シーケンス内のステートメントによって決定されたすべてのクラス変数のためのコンパイラクラスによって自動的に収集代入文ブロックと静的アクション文の合併です。静的ステートメントは、それがアクセスすることができないだけ後の変数割り当てで定義されるように、文の静的ブロックの前に変数を定義するブロックにアクセスすることができます。変数は、クラスの前で、より少ないもののサンパウロの操作を書いてみます。

パブリック クラスTest1を{ 

    静的{ 
        I = 0; // 通常のコンパイル 
        するSystem.out.println(I); // コンパイルが失敗
    } 

    静的 INT I = 1 ; 

}

<clinit>()メソッド及びクラスインスタンスコンストラクタ<初期化>()メソッドは、明示的なコール親クラスのコンストラクタを除い2、。

図3に示すように、親クラス<clinit>()メソッドが最初に実行される、クラス<clinit>最初の実行()メソッドは、java.lang.Objectのこと、したがって親クラス変数優先サブクラス内のステートメントの静的ブロックしなければなりません割り当て。

図4に示すように、クラスまたはインタフェースのための<clinit>()メソッドは、クラスが文やクラス変数代入の静的ブロックでない場合、コンパイラはクラス<clinit>()メソッドのために生成されないことが、必要ではありません。

図5に示すように、インターフェースは、静的ステートメントブロックを使用するだけでなく、<clinit>()メソッドを生成することができません。インターフェース<clinit>()メソッドは、親インタフェース<clinit>()メソッドを実行する必要がない場合、<clinit>()インターフェースメソッド、インターフェイスで定義された親唯一の変数、インタフェースの実装クラスの初期化が行われません実装使用時には、親インタフェースが(インタフェースは、あなただけで一定ではないのですか?)を初期化します。

図6に示すように、クラスの仮想保証は<clinit>()メソッドは、適切に、マルチスレッド環境において同期をロックされています。したがって、1つのスレッドのみ<clinit>()メソッドのこのタイプを実行するために、他のスレッドがブロックされるであろう。

3、クラスローダ

  クラスのロード・フェーズと呼ばれるこの操作のコードモジュールを達成するために、JVMの外に達成されるアクション「の完全修飾クラス名を通じて、バイナリバイトストリームの説明を取得するには、」「クラスローダを。」

  任意のカテゴリについて、あなたはそれがJava仮想マシンでその独自性とクラスローダとクラス自体によって確立されたロードする必要があり、各クラスローダは、別のクラスの名前空間を持っています。

3.1クラスローダタイプ

  Java仮想マシンの観点から、クラスローダのクラスは、ブートローダ(ブートストラップクラスローダ)と他のクラスローダの2種類に分けることができる他のクラスローダは、Java言語で実装されている間、ブートクラスローダーは、仮想マシン自体の一部であり、すべての抽象クラスjava.lang.ClassLoaderの由来。開発者の視点から、システムは、クラスローダは三種類に分けることができる提供します。

1.クラスローダ:

  クラスローダは、ライブラリは、仮想マシンのメモリにロードされる(例えばrt.jarのように、ファイル名のみとして識別される)<JAVA_HOME> \ libディレクトリに責任があり、仮想マシンが認識されています。スタートクラスローダは、直接Javaプログラムを参照することはできません。

2、拡張クラスローダ:

  このクラスローダはlib \ extディレクトリ\ <JAVA_HOME>をロードするための責任がある、またはすべてのライブラリにより、開発者のjava.ext.dirsシステム変数指定されたパスが直接使用するクラスローダを拡張することができます。

3、アプリケーションクラスローダ:

  クラスローダは、クラスローダgetSystemClassLoader()メソッドの戻り値であり、それはまた、システムクラスローダと呼ばれます。それがクラスパスのライブラリに指定されたロードを担当して、プログラムはデフォルトのクラスローダです。

3.2親委譲モデル

  親委任モデルに加えて、クラスローダの残りの部分は、自身の親クラスローダを持っている必要があり、トップレベルのブートローダーのクラスが必要です。クラスローダとの間のこの親子関係は、継承ではなく、親と多重化の組み合わせの関係ローダコードで実装されていません。以下に示すように、親委譲モデル:

  親委任モデルは、作業プロセスです:クラスローダは、クラスが要求を受け取っロードする場合、それは親クラスローダに委譲し、この要求を置く場合は、すべてのロード要求は、ブートクラスローダのトップに送信されますので、完了すると、検索親ローダーはちょうど読み込み、それを自分でしようとする目的のカテゴリ、サブローダーを見つけることができません。

  使用双亲委派模型的其中一个好处是Java类随类加载器一起具备了一种带有优先级的层次关系。比如java.lang.Object类由启动类加载器加载,因此其他所有的类使用的都会是同一个Object类。

3.3 破坏双亲委派模型

  双亲委派模型并不是强制性的约束模型,有几种大规模的非双亲委派模型情况:

1、双亲委派模型很好的解决了各个类加载器的基础类统一问题,但如果基础类又要调用回用户的代码,Java设计团队引入了线程上下文类加载器。这个类加载器可以通过java.lang.Thread类的setContextClassLoaser()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过,那这个类加载器默认就是应用程序类加载器。

おすすめ

転載: www.cnblogs.com/liuwy/p/11093923.html