ホームページ  >  記事  >  Java  >  Javaクラスロードメカニズム

Javaクラスロードメカニズム

(*-*)浩
(*-*)浩転載
2019-08-27 16:21:192592ブラウズ

私は長い間、Java のクラス ロード メカニズムを理解するのが難しすぎると考えていたため、非常に抵抗がありました。しかし、優れた Java エンジニアになるために、私は覚悟を決めて勉強することにしました。

Javaクラスロードメカニズム

01. バイトコード

Java クラスのロード メカニズムについて説明する前に、まず Java バイトコードを理解する必要があります。クラスロードメカニズムと密接に関連しています。

コンピュータは 0 と 1 のみを認識するため、どの言語で書かれたプログラムでも、コンピュータで理解して実行するにはマシンコードにコンパイルする必要があります。Java も例外ではありません。

Java が誕生したとき、「一度書けばどこでも実行できる」という素晴らしいスローガンを叫びました。この目標を達成するために、サンはさまざまなプラットフォーム (Windows、Linux) で実行できる多くの製品をリリースしました。 Java 仮想マシン (JVM) - Java コンパイル済みバイトコードのロードと実行を担当します。

Javaクラスロードメカニズム

Java バイトコードはどのようなものですか? 簡単なコードを使って見てみましょう。

ソース コードは次のとおりです。

package com.cmower.java_demo;

public class Test {

    public static void main(String[] args) {
        System.out.println("版权声明");
    }

}

コードがコンパイルされたら、xxd Test.class コマンドを使用してバイトコード ファイルを確認します。

xxd Test.class
00000000: cafe babe 0000 0034 0022 0700 0201 0019  .......4."......
00000010: 636f 6d2f 636d 6f77 6572 2f6a 6176 615f  com/cmower/java_
00000020: 6465 6d6f 2f54 6573 7407 0004 0100 106a  demo/Test......j
00000030: 6176 612f 6c61 6e67 2f4f 626a 6563 7401  ava/lang/Object.
00000040: 0006 3c69 6e69 743e 0100 0328 2956 0100  ..<init>...()V..
00000050: 0443 6f64 650a 0003 0009 0c00 0500 0601  .Code...........
00000060: 000f 4c69 6e65 4e75 6d62 6572 5461 626c  ..LineNumberTabl

少し混乱していますよね? ######それは正しい。

このバイトコードのカフェ ベイブは「マジック ナンバー」と呼ばれ、JVM が .class ファイルを認識するための記号です。ファイル形式のカスタマイザーは、(使用されない限り) マジック ナンバーを自由に選択できます。たとえば、.png ファイルのマジック ナンバーは 8950 4e47 です。

他のコンテンツについては、忘れることもできます。

02. クラスのロード プロセス

Java バイトコードを理解した後、Java のクラス ロード プロセスについて話しましょう。

Java のクラス読み込みプロセスは、読み込み、検証、準備、解析、初期化の 5 つの段階に分けることができます。これらの 5 つのフェーズは通常、順番に発生しますが、動的バインディングの場合、解析フェーズは初期化フェーズの後に発生します。

1) ロード

この段階での JVM の主な目的は、さまざまなデータ ソース (クラス ファイル、jar パッケージ、さらにはネットワークなど) からバイトコードをバイナリに変換して転送することです。バイト ストリームが読み込まれてメモリにロードされ、クラスを表す java.lang.Class オブジェクトが生成されます。

2) 検証

JVM はこの段階でバイナリ バイト ストリームを検証します。JVM バイトコード仕様に準拠するもののみが、JVM によって正しく実行されます。この段階は、JVM のセキュリティを確保するための重要な障壁です。ここでは、いくつかの主要なチェックを示します。

バイナリ バイト ストリーム形式が期待どおりであることを確認してください (たとえば、カフェ ベネで始まるかどうかなど)。

すべてのメソッドがアクセス制御キーワードの制限に準拠しているかどうか。

メソッド呼び出しのパラメータの数と種類が正しいかどうか。

変数が使用前に適切に初期化されていることを確認してください。

変数に適切な型の値が割り当てられているかどうかを確認してください。

3) 準備

JVM はこの段階でメモリを割り当て、クラス変数 (静的変数とも呼ばれ、static キーワードで変更されます) を初期化します (データ型のデフォルトの初期化に対応します)。値 (0、0L、null、false など)。

つまり、次のようなコードがある場合:

public String chenmo = "沉默";
public static String wanger = "王二";
public static final String cmower = "沉默王二";

chenmo にはメモリが割り当てられませんが、wanger にはメモリが割り当てられますが、wanger の初期値は「王二」ではなく、ヌル。

静的finalで変更された変数は定数と呼ばれ、クラス変数とは異なりますので注意してください。定数に値が割り当てられると、その値は変更されないため、準備フェーズの cpower の値は null ではなく「silent king two」になります。

4) 解決策

このステージでは、定数プール内のシンボル参照を直接参照に変換します。 ######何?シンボリック参照、直接参照?

シンボル参照では、一連のシンボル (使用時にターゲットを明確に特定できる限り、任意の形式のリテラル) を使用して、参照されるターゲットを記述します。

コンパイル時には、Java クラスは参照されるクラスの実際のアドレスを知らないため、代わりにシンボリック参照のみを使用できます。たとえば、com.Wanger クラスは com.Chenmo クラスを参照しますが、コンパイル時に Wanger クラスは Chenmo クラスの実際のメモリ アドレスを知らないため、シンボル com.Chenmo のみを使用できます。

直接参照は、シンボル参照を解析して、参照の実際のメモリ アドレスを見つけます。

5) 初期化

このフェーズは、クラス読み込みプロセスの最後のステップです。準備フェーズでは、クラス変数にはデフォルトの初期値が割り当てられ、初期化フェーズでは、クラス変数にはコードが予期する値が割り当てられます。つまり、初期化フェーズはクラスのコンストラクターメソッドを実行するプロセスです。

ああ、いや、上の段落は非常に抽象的でわかりにくいですよね? 例を挙げてみましょう。

String cpower = new String("Silent Wang Er");

上記のコードは new キーワードを使用して文字列オブジェクトをインスタンス化し、この時点で String のコンストラクターが呼び出されます。クラスは cpower をインスタンス化します。

03. クラス ローダー

クラス ロード プロセスについて話した後は、クラス ローダーについて話さなければなりません。

一般来说,Java 程序员并不需要直接同类加载器进行交互。JVM 默认的行为就已经足够满足大多数情况的需求了。不过,如果遇到了需要和类加载器进行交互的情况,而对类加载器的机制又不是很了解的话,就不得不花大量的时间去调试

ClassNotFoundException 和 NoClassDefFoundError 等异常。

对于任意一个类,都需要由它的类加载器和这个类本身一同确定其在 JVM 中的唯一性。也就是说,如果两个类的加载器不同,即使两个类来源于同一个字节码文件,那这两个类就必定不相等(比如两个类的 Class 对象不 equals)。

站在程序员的角度来看,Java 类加载器可以分为三种。

1)启动类加载器(Bootstrap Class-Loader),加载 jre/lib 包下面的 jar 文件,比如说常见的 rt.jar。

2)扩展类加载器(Extension or Ext Class-Loader),加载 jre/lib/ext 包下面的 jar 文件。

3)应用类加载器(Application or App Clas-Loader),根据程序的类路径(classpath)来加载 Java 类。

来来来,通过一段简单的代码了解下。

public class Test {

	public static void main(String[] args) {
		ClassLoader loader = Test.class.getClassLoader();
		while (loader != null) {
			System.out.println(loader.toString());
			loader = loader.getParent();
		}
	}

}

每个 Java 类都维护着一个指向定义它的类加载器的引用,通过 类名.class.getClassLoader() 可以获取到此引用;然后通过 loader.getParent() 可以获取类加载器的上层类加载器。

这段代码的输出结果如下:

sun.misc.Launcher$AppClassLoader@73d16e93
sun.misc.Launcher$ExtClassLoader@15db9742

第一行输出为 Test 的类加载器,即应用类加载器,它是 sun.misc.Launcher$AppClassLoader 类的实例;第二行输出为扩展类加载器,是 sun.misc.Launcher$ExtClassLoader 类的实例。那启动类加载器呢?

按理说,扩展类加载器的上层类加载器是启动类加载器,但在我这个版本的 JDK 中, 扩展类加载器的 getParent() 返回 null。所以没有输出。

04、双亲委派模型

如果以上三种类加载器不能满足要求的话,程序员还可以自定义类加载器(继承 java.lang.ClassLoader 类),它们之间的层级关系如下图所示。

Javaクラスロードメカニズム

这种层次关系被称作为双亲委派模型:如果一个类加载器收到了加载类的请求,它会先把请求委托给上层加载器去完成,上层加载器又会委托上上层加载器,一直到最顶层的类加载器;如果上层加载器无法完成类的加载工作时,当前类加载器才会尝试自己去加载这个类。

PS:双亲委派模型突然让我联想到朱元璋同志,这个同志当上了皇帝之后连宰相都不要了,所有的事情都亲力亲为,只有自己没精力没时间做的事才交给大臣们去干。

使用双亲委派模型有一个很明显的好处,那就是 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系,这对于保证 Java 程序的稳定运作很重要。

上文中曾提到,如果两个类的加载器不同,即使两个类来源于同一个字节码文件,那这两个类就必定不相等——双亲委派模型能够保证同一个类最终会被特定的类加载器加载。

以上がJavaクラスロードメカニズムの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcsdn.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。