ノート(5)を読んで、「Java仮想マシンの深い理解」(第3版):第VII章VMのクラスローディング機構
記事のディレクトリ
読むノート(d)の クラスA記録されたファイルの保存形式のは、第VII章はおよそ仮想マシンにロードされた後に何が起こるかだけでなく、これらのクラスファイルをロードする方法で話していました。ラインと、この章を終えたが、より良い効果を見積もる食べて、JVMのクラスローディングについてのビデオを見るためにBスタンドに行きます...
パースや変換、Java仮想マシンの負荷データをメモリにクラスファイルからクラスを記述し、データ検証:テキストの開始前に、仮想マシンのクラスローディング機構と呼ばれるもののアカウントを与えるために最終的にはJavaのクラスローディング機構の種類を形成する初期化は、このプロセスは、仮想マシンで、仮想マシンで直接使用することができます。
クラスのロード時間
仮想マシンの種類を起動するメモリをアンロードするためのメモリ、最大にロードされ、ライフサイクル全体の負荷(ロード)を通過します、検証(検証)、準備(準備)、分析(決議)、初期設定(初期化)、使用(使用)及びアンローディング(アンロード)した検証、製剤は、集合的に接続された3つの部分を解析する(リンク7段階)。
ローディング、検証、製造、初期化およびアンロードシーケンスの5段階のステップ処理によって負荷ステップのタイプを決定することで、この順序で必ずしも解析段階を開始しなければなりません。これは、初期化段階の後に開始することができます。
「Java仮想マシン仕様は、」厳格なルールがありますし、唯一の6例はすぐにクラスを持っている初始化
(ロード、検証を、この前に開始する自然の必要性を準備します):
-
型が初期化されていない場合、時間は、処罰する必要が初期化フェーズは、4つの命令を生成することが可能であることが出会い、新しい、getstatic、putstatic、invokestatic 4つのバイトコード命令典型的なシナリオがあります。
- ときにオブジェクトをインスタンス化する新しいキーワードを使用します
- 読み取りまたは静的フィールドタイプを設定し(最後の変更は、コンパイラは、静的フィールド以外の定数プールへの結果となっている)とき
- 時間のタイプの静的メソッドを呼び出し
-
コールのタイプを反映するjava.lang.reflectのパッケージのアプローチを使用する場合はタイプが初期化されていない場合、あなたはその初期化をトリガーする必要があります。
-
私たちは親クラスが初期化されていない見つけた場合、クラスの初期化時には、あなたは親クラスのトリガ初期化する必要がある場合
-
仮想マシンが起動すると、利用者のニーズが実行されるメインクラスを指定するには、仮想マシンは、このマスタークラスを初期化します
-
インタフェースの実装クラスの初期化が発生した場合、インターフェイスは、このインターフェイスは、その前に初期化されることを、デフォルトの方法JDK8新規参入を定義した場合。
-
最終的な分析結果java.lang.invoke.MethodHandle例REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecialハンドル4つの方法のタイプ、および初期化されていないに対応するこの方法ハンドルクラス場合、初期化は、それをトリガするために必要とされます。
インターフェースはまた、ローディング・プロセスを有し、クラスローディングプロセスは同じではない、ローカルインタフェースとクラスとの間の差は、上述の6種類である「唯一無二」初期化は、第3のシナリオをトリガするために必要な次の場合に必要となるクラスの初期化、すべての親クラスが初期化されているが、初期化中のインターフェイスは、インターフェイスだけで、すべての初期化を完了するために、親を必要としない親のインタフェースを使用してリアルタイムに初期化されます。
クラスのロードプロセス
ロード
まず、クラスの完全修飾名によって、バイナリバイトストリームの定義を取得するには、次に静的記憶のバイトストリームを表します:ロード・プロセスでは、JVMは、主に3つのことを行うにはランタイムデータ構造領域法に構造と、メモリ内の最後のは、このクラスのjava.lang.Classオブジェクトの代表は、このクラスの各種データ入力領域にアクセスするための方法を生成します。
- アレイ型コンポーネント(コンポーネントタイプは、寸法、注意を削除し、前部要素型領域を分離する配列型を指す)参照型である場合、現在装填部の再帰的な使用は、定義されたコンポーネントタイプをロードしますアレイは、コンポーネントクラスローダクラスの名前空間のローディングタイプに識別されます。
- コンポーネントタイプが配列参照型(成分int型のために、例えば、INT []配列)されていない場合、Java仮想マシンは著しい配列でブートクラスローダに関連付けられました。
- そのアクセシビリティコンポーネントタイプのアクセシビリティと一致する配列クラスコンポーネントタイプが参照タイプでない場合、そのアクセシビリティ配列クラスは、あろう公衆にデフォルト設定は、すべてのクラスとインタフェースをアクセスすることができます。
- ローディング段階の後に、仮想マシンに格納されたフォーマットに応じて、Java仮想マシン外部バイナリバイトストリームを処理ゾーンに設定されている、方法エリアデータ格納形式は、完全に仮想マシン自体は、「Java仮想マシンによって定義された実装しましたこの地域の仕様」具体的なデータ構造は、所定されていません。適切な配置型のデータ領域法の後に、データ型領域の外部インタフェースプログラムへのアクセス方法として、Javaヒープメモリ内のオブジェクトをjava.lang.Classクラスのオブジェクトをインスタンス化します。
- ローディング段階と操作部の接続フェーズ(例えば、ファイル形式のバイトコード検証動作の一部)は、ローディング・フェーズが完了していないが、接続フェーズが開始している可能性が交差行われ、それらが操作のローディング段階に巻き込まれ、まだ接続されステージの一部は、開始時間は、これらの2つの段階が一定の順序を維持しました。
検証
ファイルはバイトストリームは、すべての制約が情報が自分自身の安全を危険にさらすことなく、仮想マシンを実行するためのコードとして扱われることを保証するために、「Java仮想マシン仕様」を必要満たしている含まれていることを確認するために、クラス情報。
レディ
準備フェーズは、正式にクラス変数(すなわち、静的変数は、修飾された静的変数)がメモリを割り当て、ステージクラス変数の初期値を設定に定義されています。このときメモリの割り当ては、クラス変数を含み、インスタンス変数が含まれていないオブジェクトをJavaヒープにインスタンス化されるとき、インスタンス変数は、オブジェクトに沿って割り当てられます。
解決
直接の参照を置き換えるプロセスへのシンボリック参照のJava仮想マシンの定数プール。
初期化
初期化フェーズは、クラスコンストラクタ実行される<clinit>()
プロセスアプローチを。<clinit>()
メソッドは、プログラマが直接Javaコードを記述していない、それが自動的に製品javacコンパイラです。
<clinit>()
これは合併順次コンパイラは、ステートメントによって収集されるすべてのクラスと静的変数代入文ブロック操作(静的{}ブロック)ステートメントのコンパイラクラスによって自動的に収集されますソース・ファイルの出現の順序を決定する、ブロックの静的ステートメントは、ステートメントのブロックで定義された静的変数にのみアクセスする前に、静的ブロックの前にその背後にある変数の定義を割り当てることができることができるが、アクセスすることはできません。
<clinit>()
クラスコンストラクタ(すなわち、仮想マシンのインスタンスに透視で構成されている方法<init>()
方法)異なるが、それが明示的に確保するために、Java仮想マシンが親クラスのコンストラクタを呼び出すないサブクラスの<clinit>()
メソッドを親クラスの実行前に<clinit>()
()メソッドが実行を終了しました。したがって、第1のJava仮想マシンで<実行される<clinit>()
方法の種類は、java.lang.Objectのなければなりません。静的に定義された親クラスステートメントブロックが優先サブクラス変数の割り当てを取ります。
クラスは、ステートメントの静的なブロックでない場合は、変数の割り当てはありません、コンパイラは、クラスを生成することができない<clinit>()
方法を。
インターフェイスブロック静的ステートメントを使用することはできないが、それでも変数割り当てが初期化され、したがって、同じクラスインターフェースを生成<clinit>()
する方法を。しかし、差はクラスインターフェイスであり、インターフェースが実装<clinit>()
親インタフェース実行する必要がない<clinit>()
方法は、インターフェイスで定義された変数を使用しているだけの親から、親インターフェイスを初期化します。さらに、インターフェイスが同じ場合、初期化しないで実装するインターフェースの実装クラス<clinit>()
方法。
Java仮想マシンがしなければならないクラスのことを確実に<clinit>()
する方法が正しく、マルチスレッド環境でロックされて同期されている複数のスレッドがクラスを初期化する場合は、のみ、このクラスを実行するために、1つのスレッドがあるでしょう<clinit>()
この方法は、他のスレッドは、アクティブなスレッドが終了するまで待つ必要ブロックされている<clinit>()
方法を。
クラスローダ
クラスローダのクラス階層が分割され、OSGiの、熱い展開、コードの暗号化や他の分野の輝き、Javaテクノロジは、デザインアプレットのニーズを満たすために、もともとシステムの重要な礎石となっています今アプレットは、(ブラウザの側面を)排除しました。
クラスとクラスローダ
任意のクラスのために、共同でだけでそれとJava仮想マシンでその独自性を持つクラス自体は、クラスが2「等しい」を比較することでロードされたクラスローダによって確立されなければなりませんこの2つのクラスが前提の下で行われても、同じファイルからこの2つのクラスクラスならば、限り、異なるそれらをロードするクラスローダとして、Java仮想マシンがロードされ、それ以外の場合は、同じクラスローダをロードするために理にかなっていますこれら2つのクラスが確実に一致しないということ。
この概念は、私はシュシュを再現するためのコードを見たのは初めてです。
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
ClassLoader myLoader=new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String filaName=name.substring(name.lastIndexOf(".")+1)+".class";
InputStream is=getClass().getResourceAsStream(filaName);
if(is==null){
return super.loadClass(name);
}
byte[] b=new byte[is.available()];
is.read(b);
return defineClass(name,b,0,b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
Object obj=myLoader.loadClass("exam.offer.day05.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof exam.offer.day05.ClassLoaderTest);
}
}
結果:
class exam.offer.day05.ClassLoaderTest
false
上記のコードは、単純なクラスローダ、ロードクラスとexam.offer.day05.ClassLoaderTestおよびインスタンス化とを構築することです。
Java仮想マシンがClassLoaderTest 2つのクラスを存在し、1がロードされた仮想マシンのクラスローダの適用によるものである、他はかかわらず、私たちの習慣をロードするクラスローダであります彼らは同じクラスファイルから来るが、Java仮想マシンではまだ結果は自然物をfalseに属するチェックを入力しない2つの互いに独立したクラスです。
親委譲モデル
Java仮想マシンの観点から、2つだけ異なるクラスローダがあり、ブートクラスローダーは、C ++インプリメンテーションは、仮想マシンの一部を用いて、(BootstrapClassLoader)であり;そして一つのJavaによって実装他のクラスローダの全てが、仮想マシンの外部に独立して存在することであり、すべてが抽象クラスjava.lang.ClassLoaderの由来します。
親クラスローディング委譲モデル階層以下のように:
ブートクラスローダ(ブートストラップクラスローダ):<JAVA_HOME> \ libディレクトリに格納されている、またはされたロードを担当する-Xbootclasspath
パスに保存されたパラメータで指定され、およびJava仮想マシンがライブラリを識別することができます仮想マシンがメモリにロードされます。
拡張クラスローダ(拡張クラスローダー):このクラスsun.misc.Launcher $ ExtClassLoaderは、Javaコードの形で実装クラスローダです。これは、ロード<JAVA_HOME> \ libにextディレクトリまたは指定されているのjava.ext.dirsシステム変数パス\ライブラリのすべてを担当しています。
アプリケーションクラスローダ(アプリケーションクラスローダ):このクラスローダはsun.misc.Launcher $ AppClassLoaderによって実装されます。アプリケーションクラスローダがgetSystem、クラスローダのClassLoaderクラスの戻り値()メソッドであるため、そういくつかの場合は、「システムクラスローダ」と呼ばれること これは、ユーザークラスパス(クラスパス)上のすべてのライブラリをロードするための責任があります。
作業プロセスの親委任モデルは次のとおりです。クラスローダがロードクラスが要求を受信した場合、それは最初に自身のこのクラスをロードしようとしませんが、親クラスローダにこの要求を委任しますすべての負荷要求は、最終的なトップレベルのブートローダーのクラスに送られるべきであるように、クラスローダの各レベルを完了するためには、真である、親ローダフィードバック彼らはロード要求を完了することができないだけで(その検索必要なクラスが)見つからない場合、子ローダは、独自の負荷を完了しようとします。
なぜ親はモデルを委任しますか?安全性は、することができ、負荷にコアクラスの重複を避け、改ざんされることを避けます。私たちはクラスパス年で呼ばれるjava.lang.Objectクラスを書いた場合、システムは、Objectクラスの数が表示されますが、このクラスは正常にコンパイルすることができますが、委任モデルの親を使用しますが、実行するためにロードすることはできません。最初は、最もトップクラスjava.lang.Objectのクラスローダローディングシステムであるため、究極のカスタムクラスローダは、java.lang.Objectクラスをロードすることはできません。
具体的に全体の作業プロセスを理解するために、表情で、ソースコード、ソースコードやコメントは対照的で簡単に見ています。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
破壊両親委譲モデル
三回全体のダメージがあります。
- > 最初の損傷は:親委任モデルが表示される前に発生します。)(親委任モデルはJDK1.2後に浮上しているが、ユーザ定義のクラスローダは、既存のコードとの互換性のために、親委任モデルは、あなただけではjava.lang.ClassLoaderに新しいプロテクトメソッドにfindClassを追加することができますので、存在していますこのメソッドをオーバーライドすることが可能で、ガイドはむしろのloadClassより()、ユーザクラスローダロジックにより調製しました。findClass()をオーバーライドする場合、及び、同様行うクラスとクラスローダコードの一部は、印刷結果に見出すことができることは事実です。テストは、例を探すことができます- > 親の委譲モデルを破壊する方法
- > 第二には、破壊され、モデル自体の欠陥が親が同じ問題が根底にあるクラス、各クラスローダ(上部ローディングの基本クラスローダ)に対する良好な解決策を任命引き起こさベース。彼らは常に、APIのユーザーコードとして起動されているため、クラスは「土台」と呼ばれているが、物事には、多くの場合、完璧されていません。
あなたはユーザーコードに、基本クラスのバックを呼び出す必要があれば、何をしますか?
典型的な例は、JNDIは現在、標準のJavaサービス、(JDK1.3ときrt.jar内にそれらへの)負荷にブートクラスローダからそのコードが、JNDI JNDIサービスです目的は、経営資源や外観を一元化することで、それは独立したベンダーによってクラスパスアプリケーションでJNDIインタフェースプロバイダを呼び出すためのコードを実装して展開する必要がありますが、ブートクラスローダは、これらのコードを「知る」ことができません。
スレッドコンテキストクラスローダ(スレッドコンテキストクラスローダ):この問題を解決するために、Javaの設計チームは、あまりエレガントなデザインを導入しました。このクラスローダは、あなたがスレッドを作成するときに()メソッドjava.lang.Threadのクラスが設定されていないアプリケーションでグローバルに設定されていない場合、彼は、親スレッドから継承されますsetContextClassLoaderによって設定することができます、その後、クラスローダは、デフォルトのアプリケーションクラスローダです。
ローダスレッドコンテキストで、JNDIサービスは、サブクラスローディング動作を完了するための親クラスローダクラスローダ要求、この実際の動作であり、必要とされるSPIコードをロードするために使用することができ事は、実際に一般的な原則の親委任モデルに違反したクラスローダを使用して逆に親委任モデル階層を介して取得することですが、また無力。すべてのJavaロード操作はJNDI、例えば、基本的にSPIこの方法を必要とするJDBC、JCE、JBI JAXBなどが挙げられます。
- > 三破壊:ホットコード交換用モジュールホットデプロイ、短い答え:ユーザーのダイナミクスが発生したため、プログラムの追求、ここでは「ダイナミック」と呼ばれ、現在までにいくつかの非常に「熱い」名詞を指し、それは限り展開が使用できるようになりますよう、マシンを再起動しないと言うことです。
JDBCには、例えば、親委任モデルの例は精巧に破壊されます。ネイティブJDBCタイプは、動的にデータベースドライバクラスの種類をロードするためのJDBCドライバのクラスの必要性を読み込まBootstrapClassLoaderてパッケージrt.jar内に配置され、MySQLのコネクタ-の.jarドライバーのクラスがあるさユーザーは、それは私が書いたコードであるため、クラスローダは確かに、ロードすることはできません開始し、独自のコードを記述し、このモデルを解決することができ、それはスレッドコンテキストクラスローダによってクラスを開始したアプリケーションクラスによってロードされる必要があるであろう両親を委任されていますスムーズなロードの違反。
Javaのモジュラーシステム
JDK9モジュラーシステムが導入されます。モジュラーカプセル化が有効になっている場合、モジュールができ、明示的に宣言し、他のモジュールに依存して、Java仮想マシンが起動することができるようになりますので、検証良い設定するアプリケーションの開発フェーズを依存関係は、実行時に完了しているが見つからない場合に、したがって、異常によって引き起こさ依存の種類に実行し、大部分を避けて、直接起動に失敗しました。
クラスローダモジュラー
1.拡張クラスローダ(拡張クラスローダ)置換されたクラスローダプラットフォーム(プラットフォームクラスローダ)
2.プラットフォームのクラスローダおよびアプリケーション・クラス・ローダーは、もはやjava.net.URLClassLoaderから派生されていません