クラスローディングサブシステム
概要概要
全体像は以下の通りです
Java仮想マシンを自分で作成する場合、どのような構造を検討する必要がありますか?
- クラスローダー
- 実行エンジン
クラスローダーサブシステムの役割
クラスローダーサブシステムは、ファイルシステムまたはネットワークからクラスファイルをロードする役割を果たし、クラスファイルにはファイルの先頭に特定のファイル識別子があります。
ClassLoaderは、クラスファイルのロードのみを担当します。実行できるかどうかは、ExecutionEngineによって決定されます。
ロードされたクラス情報は、メソッド領域と呼ばれるメモリ空間に格納されます。クラス情報に加えて、実行時定数プール情報もメソッド領域に格納され、文字列リテラルと数値定数を含めることもできます(定数情報のこの部分は、クラスファイルの定数プール部分のメモリマッピングです)。
- クラスファイルはローカルハードディスクに存在します。これは、設計者が紙に描いたテンプレートとして理解できます。最後に、このテンプレートが実行されると、このテンプレートがJVMに読み込まれ、このファイルに基づいてn個の同一のインスタンスがインスタンス化されます。
- クラスファイルは、DNAメタデータテンプレートと呼ばれるJVMにロードされ、メソッド領域に配置されます。
- .classファイル-> JVM->は、最終的にメタデータテンプレートになります。このプロセスでは、宅配便の役割を果たすために輸送ツール(クラスローダー)が必要です。
クラスの読み込みプロセス
たとえば、次の簡単なコード
/**
* 类加载子系统
*/
public class HelloLoader {
public static void main(String[] args) {
System.out.println("我已经被加载啦");
}
}
そのロードプロセスは何ですか?
完全なフローチャートを以下に示します
読み込みフェーズ
クラスの完全修飾名を使用して、このクラスを定義するバイナリバイトストリームを取得します
このバイトストリームによって表される静的ストレージ構造をメソッド領域のランタイムデータ構造に変換します
メソッド領域でこのクラスのさまざまなデータへのアクセスエントリとして、メモリ内のこのクラスを表すjava.lang.Classオブジェクトを生成します。
クラスファイルをロードする方法
- ローカルシステムから直接ロードする
- インターネット経由で取得、典型的なシナリオ:Webアプレット
- zipアーカイブから読み取り、将来的にjarおよびwar形式の基礎になります
- 実行時の計算と生成、最もよく使用されるのは次のとおりです。動的プロキシテクノロジ
- 他のファイルによって生成される、典型的なシナリオ:JSPアプリケーションはプロプライエタリデータベースから.classファイルを抽出しますが、これは比較的まれです。
- 暗号化されたファイルから取得され、クラスファイルの逆コンパイルに対する一般的な保護対策
リンクフェーズ
確認
目的は、クラスファイルのバイトストリームに含まれる情報が現在の仮想マシンの要件を満たしていることを確認し、ロードされたクラスの正確性を確認し、仮想マシン自体のセキュリティを危険にさらさないようにすることです。
これには主に、ファイル形式の検証、メタデータの検証、バイトコードの検証、およびシンボル参照の検証の4つの検証が含まれます。
ツール:バイナリビューアビュー
不正なバイトコードファイルが表示された場合、検証は失敗します
同時に、IDEAプラグインをインストールすることでクラスファイルを表示できます
インストールが完了したら、クラスファイルをコンパイルした後、[表示]をクリックして、インストールしたプラグインを表示し、バイトコードメソッドを表示します。
準備する
クラス変数にメモリを割り当て、クラス変数のデフォルトの初期値、つまりゼロ値を設定します。
/**
*/
public class HelloApp {
private static int a = 1; // 准备阶段为0,在下个阶段,也就是初始化的时候才是1
public static void main(String[] args) {
System.out.println(a);
}
}
上記の変数aには、準備フェーズで初期値が割り当てられますが、1ではなく0です。
finalで変更された静的なものはここには含まれていません。これは、finalがコンパイル中に割り当てられ、準備フェーズで明示的に初期化されるためです。
ここでは、インスタンス変数は割り当てられず、初期化されません。クラス変数はメソッド領域に割り当てられ、インスタンス変数はオブジェクトとともにJavaヒープに割り当てられます。
たとえば、次のコード
解決する
定数プール内のシンボル参照を直接参照に変換するプロセス。
実際、初期化が実行された後、解析操作には多くの場合JVMが伴います。
シンボル参照は、参照されるターゲットを説明するための一連のシンボルです。シンボル参照のリテラル形式は、「Java仮想マシン仕様」のクラスファイル形式で明確に定義されています。直接参照は、ターゲットを直接指すポインター、相対オフセット、またはターゲットに間接的に配置されているハンドルです。
解析アクションは、主にクラスまたはインターフェイス、フィールド、クラスメソッド、インターフェイスメソッド、メソッドタイプなどを対象としています。定数プール内のCONSTANTクラス情報、CONSTANT Fieldref情報、ConstantMethodref情報などに対応
初期化フェーズ
初期化フェーズは、クラスコンストラクターメソッド()を実行するプロセスです。
このメソッドを定義する必要はありません。javacコンパイラは、クラス内のすべてのクラス変数の割り当てアクションを自動的に収集し、静的コードブロック内のステートメントをマージします。
- つまり、コードに静的変数が含まれている場合、clinitメソッドがあります
コンストラクターメソッドの命令は、ステートメントがソースファイルに表示される順序で実行されます。
()クラスのコンストラクターとは異なります。(関連付け:コンストラクターは仮想マシンの観点からは()です。)クラスに親クラスがある場合、JVMは、サブクラスの()が実行される前に、親クラスの()が実行されたことを確認します。
- クラスが宣言されると、コンストラクターが生成されます。デフォルトは空のパラメーターコンストラクターです。
public class ClassInitTest {
private static int num = 1;
static {
num = 2;
number = 20;
System.out.println(num);
System.out.println(number); //报错,非法的前向引用
}
private static int number = 10;
public static void main(String[] args) {
System.out.println(ClassInitTest.num); // 2
System.out.println(ClassInitTest.number); // 10
}
}
親クラスが関与する場合の変数割り当てプロセスについて
public class ClinitTest1 {
static class Father {
public static int A = 1;
static {
A = 2;
}
}
static class Son extends Father {
public static int b = A;
}
public static void main(String[] args) {
System.out.println(Son.b);
}
}
出力結果は2です。つまり、最初にClinitTest1をロードすると、mainメソッドが見つかり、Sonの初期化が実行されますが、SonはFatherを継承するため、Fatherの初期化を実行する必要があり、同時にAが2に割り当てられます。 。逆コンパイルによってFatherの読み込みプロセスを取得します。最初に、元の値が1に割り当てられ、次に2にコピーされ、最後に返されることがわかります。
iconst_1
putstatic #2 <com/atguigu/java/chapter02/ClinitTest1$Father.A>
iconst_2
putstatic #2 <com/atguigu/java/chapter02/ClinitTest1$Father.A>
return
仮想マシンは、クラスの()メソッドが同期され、複数のスレッドでロックされていることを確認する必要があります。
public class DeadThreadTest {
public static void main(String[] args) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 线程t1开始");
new DeadThread();
}, "t1").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 线程t2开始");
new DeadThread();
}, "t2").start();
}
}
class DeadThread {
static {
if (true) {
System.out.println(Thread.currentThread().getName() + "\t 初始化当前类");
while(true) {
}
}
}
}
上記のコード、出力結果は次のとおりです。
线程t1开始
线程t2开始
线程t2 初始化当前类
以上のことから、初期化後の初期化は1回のみであり、同期ロックのプロセスであることがわかります。
クラスローダーの分類
JVMは、2種類のクラスローダーをサポートしています。それらは、BootstrapClassLoaderとUser-DefinedClassLoaderです。
概念的には、カスタムクラスローダーは通常、プログラム内で開発者がカスタマイズしたクラスローダーを指しますが、Java仮想マシン仕様ではそのように定義されていません。代わりに、抽象クラスから派生したすべてのクラスをロードします。ClassLoaderすべてがカスタムに分割されます。クラスローダー。
クラスローダーのタイプがどのように分割されていても、以下に示すように、プログラムには常に最も一般的なクラスローダーが3つしかありません。
ここでの4つは封じ込め関係であり、上位層と下位層ではなく、サブシステムの継承関係でもありません。
クラスに合格し、さまざまなローダーを取得します
public class ClassLoaderTest {
public static void main(String[] args) {
// 获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
// 获取其上层的:扩展类加载器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);
// 试图获取 根加载器
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println(bootstrapClassLoader);
// 获取自定义加载器
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
// 获取String类型的加载器
ClassLoader classLoader1 = String.class.getClassLoader();
System.out.println(classLoader1);
}
}
得られた結果から、ルートローダーはコードから直接取得することはできず、ユーザーコードが現在使用しているローダーはシステムクラスローダーであることがわかります。同時に、String型のローダーを取得し、それがnullであることを確認します。これは、String型がルートローダーを介してロードされることを意味します。つまり、Javaのコアクラスライブラリはルートローダーを使用してロードされます。
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1540e19d
null
sun.misc.Launcher$AppClassLoader@18b4aac2
null
仮想マシンに付属のローダー
クラスローダーを起動します(ブートストラップクラスローダー、ブートストラップクラスローダー)
- このクラスのロードはC / C ++言語で実装され、JVM内にネストされています。
- これは、JVM自体に必要なクラスを提供するために使用されるコアJavaライブラリ(JAVAHOME / jre / 1ib / rt.jar、resources.jarまたはsun.boot.class.pathパス)をロードするために使用されます。
- ava.lang.ClassLoaderから継承せず、親ローダーもありません。
- 拡張クラスとアプリケーションクラスローダーをロードし、それらを親クラスローダーとして指定します。
- セキュリティ上の理由から、Bootstrapスタートアップクラスローダーは、パッケージ名がjava、javax、sunなどで始まるクラスのみをロードします。
拡張クラスローダー
- Java言語で記述され、sun.misc.Launcher $ ExtClassLoaderによって実装されます。
- ClassLoaderクラスから派生
- 親クラスローダーはスタートアップクラスローダーです
- java.ext.dirsシステムプロパティで指定されたディレクトリからクラスライブラリをロードするか、JDKインストールディレクトリのjre / 1ib / extサブディレクトリ(拡張ディレクトリ)からクラスライブラリをロードします。ユーザーが作成したJARがこのディレクトリに配置されている場合、拡張クラスローダーによって自動的にロードされます。
アプリケーションクラスローダー(システムクラスローダー、AppClassLoader)
- javI言語で記述され、sun.misc.LaunchersAppClassLoaderによって実装されます
- ClassLoaderクラスから派生
- 親クラスローダーは拡張クラスローダーです
- これは、環境変数classpathまたはシステムプロパティjava.class.pathで指定されたパスの下にクラスライブラリをロードする役割を果たします。
- このクラスのロードは、プログラムのデフォルトのクラスローダーです。一般的に、Javaアプリケーションクラスはそれによってロードされます。
- クラスローダーは、classLoader#getSystemclassLoader()メソッドを介して取得できます。
ユーザー定義のクラスローダー
Javaの日常のアプリケーション開発では、クラスのロードは上記の3種類のローダーによってほぼ実行されますが、必要に応じて、クラスローダーをカスタマイズしてクラスのロード方法をカスタマイズすることもできます。
なぜクラスローダーをカスタマイズしたいのですか?
- クラスを分離してロードする
- クラスのロード方法を変更する
- 拡張機能の読み込みソース
- ソースコードの漏洩を防ぐ
ユーザー定義のクラスローダーの実装手順:
- 開発者は、抽象クラスava.1ang.ClassLoaderを継承して独自のクラスローダーを実装し、いくつかの特別なニーズを満たすことができます。
- JDK1.2より前では、クラスローダーをカスタマイズするときは、常にClassLoaderクラスを継承し、1oadClass()メソッドを書き直して、カスタムクラスローディングクラスを実現していましたが、JDK1.2以降では、ユーザーが1oadclass()メソッドをオーバーライドすることは推奨されなくなりました。 、ただし、findclass()メソッドでカスタムクラスローディングロジックを作成することをお勧めします
- カスタムクラスローダーを作成するときに、それほど複雑な要件がない場合は、URIClassLoaderクラスを直接継承できるため、独自のfindclass()メソッドやバイトコードストリームを取得する方法を作成する必要がなくなり、カスタムクラスローダーを作成できます。文章はより簡潔です。
ルートローダーでロードできるディレクトリを表示する
ルートローダーはjava / libディレクトリ内のクラスのみをロードできるという概念から学びました。次のコードで確認してみましょう。
public class ClassLoaderTest1 {
public static void main(String[] args) {
System.out.println("*********启动类加载器************");
// 获取BootstrapClassLoader 能够加载的API的路径
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL url : urls) {
System.out.println(url.toExternalForm());
}
// 从上面路径中,随意选择一个类,来看看他的类加载器是什么:得到的是null,说明是 根加载器
ClassLoader classLoader = Provider.class.getClassLoader();
}
}
得られた結果
*********启动类加载器************
file:/E:/Software/JDK1.8/Java/jre/lib/resources.jar
file:/E:/Software/JDK1.8/Java/jre/lib/rt.jar
file:/E:/Software/JDK1.8/Java/jre/lib/sunrsasign.jar
file:/E:/Software/JDK1.8/Java/jre/lib/jsse.jar
file:/E:/Software/JDK1.8/Java/jre/lib/jce.jar
file:/E:/Software/JDK1.8/Java/jre/lib/charsets.jar
file:/E:/Software/JDK1.8/Java/jre/lib/jfr.jar
file:/E:/Software/JDK1.8/Java/jre/classes
null
ClassLoaderについて
ClassLoaderクラス、これは抽象クラスであり、後続のすべてのクラスローダーはClassLoaderから継承します(スタートアップクラスローダーは含まれません)。
sun.misc.LauncherJava仮想マシンのエントリアプリケーションです。
ClassLoaderを取得する方法
- 現在のClassLoaderを取得します:clazz.getClassLoader()
- 現在のスレッドコンテキストのClassLoaderを取得します:Thread.currentThread()。getContextClassLoader()
- システムのClassLoaderを取得します:ClassLoader.getSystemClassLoader()
- 呼び出し元のClassLoaderを取得します:DriverManager.getCallerClassLoader()
親の委任メカニズム
Java仮想マシンは、クラスファイルにオンデマンドのロードメソッドを使用します。つまり、クラスを使用する必要がある場合、そのクラスファイルがメモリにロードされてクラスオブジェクトが生成されます。また、特定のクラスのクラスファイルが読み込まれると、Java仮想マシンは親委任モードを採用します。つまり、要求は親クラスに渡されて処理されます。これはタスク委任モードです。
動作原理
- クラスローダーがクラスロードリクエストを受信した場合、最初にロードするのではなく、親クラスのローダーにリクエストを委任して実行します。
- 親クラスローダーにまだ親クラスローダーがある場合は、さらに上向きに再帰的に委任され、要求は最終的に最上位のスタートアップクラスローダーに到達します。
- 親クラスローダーがクラスロードタスクを完了できる場合は、正常に戻ります。親クラスローダーがロードタスクを完了できない場合、子ローダーはそれを単独でロードしようとします。これは、親委任モードです。
親委任メカニズムの例
データベース接続用にjdbc.jarをロードするときは、まずjdbc.jarがSPIインターフェイスに基づいて実装されていることを知っておく必要があります。したがって、ロード時に親委任が実行され、最後にSPIがルートローダーからロードされます。次に、コアクラスにSPIインターフェイスクラスがロードされ、続いて逆委任がロードされ、実装クラスjdbc.jarがスレッドコンテキストクラスローダーを介してロードされます。
サンドボックスセキュリティメカニズム
カスタム文字列クラス。ただし、カスタム文字列クラスをロードする場合、最初にブートクラスローダーを使用してロードし、ブートクラスローダーは最初にjdkに付属するファイル(rt.jarパッケージのjava \ lang)をロードします。 )\ String.class)、rt.jarパッケージの文字列クラスがロードされているため、メインメソッドがないことを示すエラーメッセージが表示されます。これにより、サンドボックスのセキュリティメカニズムであるJavaコアソースコードを確実に保護できます。
親の委任メカニズムの利点
上記の例から、親メカニズムが次のことができることがわかります。
- クラスの繰り返しのロードを避けます
- プログラムのセキュリティを保護し、コアAPIが自由に改ざんされるのを防ぎます
- カスタムクラス:java.lang.String
- カスタムクラス:java.lang.ShkStart(エラー:java.langで始まるクラスの作成を防止します)
その他
2つのクラスオブジェクトが同じであるかどうかを判断する方法
2つのクラスオブジェクトがJVMで同じクラスであるかどうかを示すために、2つの必要条件があります。
- クラスの完全なクラス名は、パッケージ名を含めて同じである必要があります。
- このクラスをロードするClassLoader(ClassLoaderインスタンスオブジェクトを参照)は同じである必要があります。
つまり、JvMでは、これら2つのクラスオブジェクト(クラスオブジェクト)が同じクラスファイルから発生し、同じ仮想マシンによってロードされた場合でも、それらをロードするClassLoaderインスタンスオブジェクトが異なる限り、2つのクラスオブジェクトは異なる。等しい。
JVMは、タイプがブートローダーによってロードされるかユーザークラスローダーによってロードされるかを認識している必要があります。型がユーザークラスローダーによってロードされる場合、JVMはこのクラスローダーへの参照を型情報の一部としてメソッド領域に保存します。あるタイプから別のタイプへの参照を解決する場合、JVMは、これら2つのタイプのクラスローダーが同じであることを確認する必要があります。
クラスの能動的および受動的な使用
Javaプログラムでのクラスの使用は、WangDong使用とパッシブ使用に分けられます。
積極的な使用は7つの状況に分けられます:
- クラスのインスタンスを作成します
- 特定のクラスまたはインターフェイスの静的変数にアクセスするか、静的変数に値を割り当てます
- クラスIの静的メソッドを呼び出す
- リフレクション(例:Class.forName( "com.atguigu.Test"))
- クラスのサブクラスを初期化します
- Java仮想マシンの起動時に起動クラスとしてマークされるクラス
- JDK7以降の動的言語サポート:
- java.lang.invoke.MethodHandleインスタンスの分析結果REFgetStatic、REF putStatic、REFクラスに対応するinvokeStaticハンドルが初期化されていないため、初期化されます
上記の7つの場合を除いて、Javaクラスを使用する他のメソッドは、クラスの受動的な使用と見なされ、クラスの初期化にはつながりません。