JVM2:ClassLoaderの動作メカニズムの詳細な理解

1. ClassLoaderとは何ですか?

ClassLoaderは、その名前が示すように、クラスローダーです。ClassLoader関数

  • クラスをJVMにロードする責任があります
  • 各クラスを誰がロードするかを確認します(親優先の階層的ロードメカニズム)
  • クラスバイトコードをJVMで必要なオブジェクト形式に再解析します

遅延読み込み

実行中のJVMは、一度に必要なすべてのクラスをロードするのではなく、オンデマンドでロードされます。つまり、遅延ロードです。プログラムの実行中は、認識できない多くの新しいクラスが徐々に発生し、ClassLoaderが呼び出されてこれらのクラスがロードされます。ロードが完了すると、ClassオブジェクトはClassLoaderに格納され、次回リロードする必要はありません。

たとえば、特定のクラスの静的メソッドを呼び出す場合、最初にクラスをロードする必要がありますが、このクラスのインスタンスフィールドは変更されません。その後、インスタンスフィールドのクラスクラスを一時的にロードする必要はありませんが、静的メソッドは静的フィールドにアクセスするため、静的フィールドに関連するロードカテゴリである可能性があります。オブジェクトをインスタンス化するまで、インスタンスフィールドのカテゴリが読み込まれない場合があります。

クラスの読み込みのタイミングとプロセス

クラスは、仮想マシンのメモリにロードされてから、メモリからアンロードされるまで開始されます。そのライフサイクル全体には、ロード、検証、準備、分析、初期化、使用、およびアンロードの7つの段階が含まれます。その中で、検証、準備、分析の3つの部分をまとめてリンクと呼びます。
ここに写真の説明を書いてください

 

2つ、デフォルトでJavaによって提供される3つのClassLoader

JVM実行インスタンスには複数のClassLoaderがあり、異なるClassLoaderが異なる場所からバイトコードファイルをロードします。ネットワーク上のさまざまなファイルディレクトリ、さまざまなjarファイル、またはさまざまなサービスアドレスからロードできます。3つの重要なClassLoader、つまりBootstrapClassLoader、ExtensionClassLoader、およびAppClassLoaderがJVMに組み込まれています。

1.BootstrapClassLoader

JVMランタイムのコアクラスのロードを担当します。これらのクラスはJAVA_HOME / lib / rt.jarファイルにあります。一般的に使用される組み込みライブラリjava.xxx。*は、java.util。*などのすべてのファイルに含まれています。 、java.io。*、java.nio。*、java.lang。*など。このClassLoaderは特別で、Cコードによって実装され、「ルートローダー」と呼ばれます。

2.ExtensionClassLoader

スイングシリーズ、組み込みjsエンジン、xmlパーサーなどのJVM拡張クラスのロードを担当します。これらのライブラリ名は通常javaxで始まり、jarパッケージはJAVA_HOME / lib / ext/*。jarにあります。多くのjarパッケージです。

3.AppClassLoader

これは、ユーザーに直接直面するローダーであり、Classpath環境変数で定義されたパスにjarパッケージとディレクトリをロードします。私たちが書いたコードと私たちが使用するサードパーティのjarパッケージは、通常、それによってロードされます。

ネットワーク上の静的ファイルサーバーによって提供されるjarパッケージとクラスファイルの場合、jdkにはURLClassLoaderが組み込まれています。ユーザーは、標準化されたネットワークパスをコンストラクターに渡してから、URLClassLoaderを使用してリモートクラスライブラリをロードするだけです。URLClassLoaderは、コンストラクター内のさまざまなアドレス形式に応じて、リモートクラスライブラリをロードできるだけでなく、ローカルパスにクラスライブラリをロードすることもできます。ExtensionClassLoaderとAppClassLoaderはどちらもURLClassLoaderのサブクラスであり、どちらもローカルファイルシステムからクラスライブラリをロードします。

AppClassLoaderは、ClassLoaderクラスによって提供される静的メソッドgetSystemClassLoader()によって取得できます。これは、「システムクラスローダー」と呼ばれるものであり、ユーザーが通常作成するクラスコードは通常それによってロードされます。mainメソッドが実行されると、最初のユーザークラスのローダーはAppClassLoaderです。

3.ClassLoaderの推移性

プログラムの実行中に、不明なクラスが検出されました。どのClassLoaderを選択してロードしますか?仮想マシンの戦略は、呼び出し元のClassオブジェクトのClassLoaderを使用して、現在不明なクラスをロードすることです。呼び出し元のクラスオブジェクトとは何ですか?つまり、この不明なクラスに遭遇した場合、仮想マシンはメソッド呼び出し(静的メソッドまたはインスタンスメソッド)を実行している必要があります。このメソッドがハングしているクラスの場合、このクラスは呼び出し元のClassオブジェクトです。前に、各Classオブジェクトには、現在のクラスをロードするユーザーを記録するclassLoaderプロパティがあることを説明しました。

ClassLoaderは推移的であるため、遅延ロードされたすべてのクラスは、最初にメインメソッドであるAppClassLoaderを呼び出すClassLoaderに対して完全に責任を負います。

4.親の代表団

AppClassLoaderは、Classpathの下のクラスライブラリのロードのみを担当することを前述しました。ロードされていないシステムクラスライブラリが発生した場合、AppClassLoaderは、システムクラスライブラリのロードをBootstrapClassLoaderおよびExtensionClassLoaderに引き渡す必要があります。両親による任命」。

1. ClassLoaderのアーキテクチャ:

 

AppClassLoaderが不明なクラス名をロードする場合、クラスパスをすぐに検索することはありません。最初にクラス名をExtensionClassLoaderに渡してロードします。ExtensionClassLoaderをロードできる場合は、AppClassLoaderを気にする必要はありません。それ以外の場合は、クラスパスを検索します。

ExtensionClassLoaderが不明なクラス名をロードしても、すぐにextパスを検索することはありません。最初にクラス名をBootstrapClassLoaderに渡してロードします。BootstrapClassLoaderをロードできる場合は、ExtensionClassLoaderを煩わしくする必要はありません。それ以外の場合は、extパスでjarパッケージを検索します。

これらの3つのClassLoaderの間には、カスケードの親子関係が形成されます。各ClassLoaderは非常に怠惰です。作業を父親に任せてみてください。父親は、自分でそれを行うことはできません。各ClassLoaderオブジェクトには、その親ローダーを指す親プロパティがあります。このクラスをロードできる場合、正常にロードされます。そうでない場合、例外がスローされ、ClassNotFoundが見つかりません。

異なるClassLoader間の連携もあり、それらの間の連携は、親属性と親委任メカニズムを介して行われます。親の読み込み優先度が高くなります。さらに、親は共有関係も表します。複数の子ClassLoaderが同じ親を共有する場合、親に含まれるクラスはすべての子ClassLoaderによって共有されていると見なすことができます。これが、BootstrapClassLoaderがすべてのクラスローダーによって祖先ローダーと見なされる理由であり、JVMコアクラスライブラリは当然共有する必要があります

2.なぜ親の委任などのメカニズムが必要なのですか?私のローダーはもっとシンプルで簡単ではありませんか

主に安全のために、java.lang.stringをメモリにロードするカスタムclassLoaderを作成する場合。顧客にパッケージ化されています。次に、パスワードを文字列型オブジェクトとして保存します。そうすると、パスワードを自分自身に密かに送信できます。これは安全ではありません。

親の委任では、このような問題は発生しません。カスタムclassLoaderが文字列をロードすると、警戒します。最初に上記に移動して、ロードされているかどうかを確認します。ロードされた上記の文字列は、リロードせずに直接返されます。

このとき、Kong Jingがコングを持ち上げに来ました。コードは私が書いたものです。彼が入力したときに記録したり、データベースを直接コピーしたりすることはできません。ハハ、それが問題です。コードを書くことの、そして私はあなたにそれを与えるでしょう銀のブレスレットのペアと無料の昼食、JVMのセキュリティとは何の関係もありません

親の代表団を破る方法、なぜそれを破るのですか?

3.Class.forName

jdbcドライバーを使用している場合、Class.forNameメソッドを使用してドライバークラスを動的にロードすることがよくあります。

Class.forName("com.mysql.cj.jdbc.Driver");


原則として、mysqlによって駆動されるDriverクラスには静的コードブロックがあり、Driverクラスがロードされると実行されます。この静的コードブロックは、mysqlドライバーインスタンスをグローバルjdbcドライバーマネージャーに登録します。

class Driver {
  static {
    try {
       java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
       throw new RuntimeException("Can't register driver!");
    }
  }
  ...
}


forNameメソッドは、呼び出し元のClassオブジェクトのClassLoaderを使用して、ターゲットクラスをロードします。ただし、forNameはマルチパラメータバージョンも提供しているため、ロードするClassLoaderを指定できます。

Class<?> forName(String name, boolean initialize, ClassLoader cl)


この形式のforNameメソッドは、組み込みローダーの制限を打ち破ることができ、カスタムクラスローダーを使用することで、他のソースからクラスライブラリを自由にロードできます。ClassLoaderの推移性に応じて、ターゲットクラスライブラリによって参照される他のクラスライブラリも、カスタムローダーを使用してロードされます。

5.カスタムローダー

ClassLoaderには、loadClass()、findClass()、defineClass()の3つの重要なメソッドがあります。

loadClass()メソッドは、ターゲットクラスをロードするためのエントリポイントです。最初に、ターゲットクラスが現在のClassLoaderとその親に既にロードされているかどうかを確認します。見つからない場合は、親がロードを試行できるようにします。親がロードできない場合は、findClass()を呼び出します。カスタムローダー自体にターゲットクラスをロードさせます。ClassLoaderのfindClass()メソッドは、サブクラスでカバーする必要があります。ローダーが異なれば、ターゲットクラスのバイトコードを取得するために異なるロジックが使用されます。このバイトコードを取得した後、defineClass()メソッドを呼び出して、バイトコードをClassオブジェクトに変換します。 

import com.mashibing.jvm.Hello;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class T007_MSBClassLoaderWithEncription extends ClassLoader {

    public static int seed = 0B10110110;

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File f = new File("c:/test/", name.replace('.', '/').concat(".msbclass"));

        try {
            FileInputStream fis = new FileInputStream(f);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;

            while ((b=fis.read()) !=0) {
                baos.write(b ^ seed);
            }

            byte[] bytes = baos.toByteArray();
            baos.close();
            fis.close();//可以写的更加严谨

            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.findClass(name); //throws ClassNotFoundException
    }

    public static void main(String[] args) throws Exception {

        encFile("com.mashibing.jvm.hello");

        ClassLoader l = new T007_MSBClassLoaderWithEncription();
        Class clazz = l.loadClass("com.mashibing.jvm.Hello");
        Hello h = (Hello)clazz.newInstance();
        h.m();

        System.out.println(l.getClass().getClassLoader());
        System.out.println(l.getParent());
    }

    private static void encFile(String name) throws Exception {
        File f = new File("c:/test/", name.replace('.', '/').concat(".class"));
        FileInputStream fis = new FileInputStream(f);
        FileOutputStream fos = new FileOutputStream(new File("c:/test/", name.replaceAll(".", "/").concat(".msbclass")));
        int b = 0;

        while((b = fis.read()) != -1) {
            fos.write(b ^ seed);
        }

        fis.close();
        fos.close();
    }


カスタムクラスローダーは、親の委任ルールを破るのは簡単ではなく、loadClassメソッドを簡単にオーバーライドすることもできません。そうしないと、カスタムローダーが組み込みのコアクラスライブラリをロードできない場合があります。カスタムローダーを使用する場合は、その親ローダーが誰であるかを明確にし、親ローダーをサブクラスのコンストラクターに渡す必要があります。親クラスローダーがnullの場合、親ローダーが「ルートローダー」であることを意味します。

// ClassLoader 构造器
protected ClassLoader(String name, ClassLoader parent);


親委任ルールは、使用する親ローダーに応じて、3番目の親の委任、4番目の親の委任になる場合があり、常にルートローダーに再帰的に委任されます。

Class.forNameとClassLoader.loadClass

これらのメソッドは両方とも、ターゲットクラスをロードするために使用できます。これらのメソッドにはわずかな違いがあります。つまり、Class.forName()メソッドはネイティブタイプのクラスを取得できますが、ClassLoader.loadClass()はエラー。

Class<?> x = Class.forName("[I");
System.out.println(x);

x = ClassLoader.getSystemClassLoader().loadClass("[I");
System.out.println(x);

---------------------
class [I

Exception in thread "main" java.lang.ClassNotFoundException: [I
...

ダイヤモンド依存

プロジェクト管理には「DiamondDependency」と呼ばれるよく知られた概念があります。これは、ソフトウェアの依存関係により、同じソフトウェアパッケージの2つのバージョンが共存し、競合しないことを意味します。

 

 


私たちが通常使用するMavenは、この方法でダイヤモンドの依存関係を解決します。複数の競合するバージョンから1つを選択して使用します。異なるバージョン間の互換性が悪い場合、プログラムは正常にコンパイルおよび実行できません。この形式のMavenは、「フラット」依存関係管理と呼ばれます。

 

ClassLoaderを使用すると、ダイヤモンドの依存関係の問題を解決できます。ソフトウェアパッケージのバージョンが異なれば、ロードに異なるClassLoaderが使用され、異なるClassLoaderにある同じ名前のクラスは実際には異なるクラスです。URLClassLoaderを使用して簡単な例を試してみましょう。デフォルトの親ローダーはAppClassLoaderです。

$ cat ~/source/jcl/v1/Dep.java
public class Dep {
    public void print() {
        System.out.println("v1");
    }
}

$ cat ~/source/jcl/v2/Dep.java
public class Dep {
  public void print() {
    System.out.println("v1");
  }
}

$ cat ~/source/jcl/Test.java
public class Test {
    public static void main(String[] args) throws Exception {
        String v1dir = "file:///Users/qianwp/source/jcl/v1/";
        String v2dir = "file:///Users/qianwp/source/jcl/v2/";
        URLClassLoader v1 = new URLClassLoader(new URL[]{new URL(v1dir)});
        URLClassLoader v2 = new URLClassLoader(new URL[]{new URL(v2dir)});

        Class<?> depv1Class = v1.loadClass("Dep");
        Object depv1 = depv1Class.getConstructor().newInstance();
        depv1Class.getMethod("print").invoke(depv1);

        Class<?> depv2Class = v2.loadClass("Dep");
        Object depv2 = depv2Class.getConstructor().newInstance();
        depv2Class.getMethod("print").invoke(depv2);

        System.out.println(depv1Class.equals(depv2Class));
   }
}

実行する前に、依存ライブラリをコンパイルする必要があります

$ cd ~/source/jcl/v1
$ javac Dep.java
$ cd ~/source/jcl/v2
$ javac Dep.java
$ cd ~/source/jcl
$ javac Test.java
$ java Test
v1
v2
false


この例では、2つのURLClassLoaderが同じパスを指している場合、異なるClassLoaderクラスでロードされた同じバイトコードでさえ同じクラスと見なすことができないため、次の式は依然としてfalseです。

depv1Class.equals(depv2Class)


また、Depクラスの2つの異なるバージョンに同じインターフェイスを実装させることもできます。これにより、リフレクションを使用してDepクラスのメソッドを呼び出す必要がなくなります。

Class<?> depv1Class = v1.loadClass("Dep");
IPrint depv1 = (IPrint)depv1Class.getConstructor().newInstance();
depv1.print()


ClassLoaderは依存関係の競合の問題を解決できますが、リフレクションまたはインターフェイスによって動的に呼び出されるように、さまざまなソフトウェアパッケージの操作インターフェイスも制限します。Mavenにはこの制限はありません。仮想マシンのデフォルトの遅延読み込み戦略に依存しています。操作中にカスタムClassLoaderが表示されない場合は、AppClassLoaderが最初から最後まで使用され、同じ名前のクラスの異なるバージョンを読み込む必要があります。 ClassLoaderが異なるため、Mavenはダイヤモンドの依存関係を完全に解決できません。

ダイヤモンドの依存関係を解決できるオープンソースのパッケージ管理ツールがあるかどうかを知りたい場合は、AntFinancialのオープンソースの軽量クラス分離フレームワークであるsofa-arkについて学ぶことをお勧めします。

 

Thread.contextClassLoader

Threadのソースコードを少し読むと、そのインスタンスフィールドに非常に特別なフィールドがあります。

class Thread {
  ...
  private ClassLoader contextClassLoader;

  public ClassLoader getContextClassLoader() {
    return contextClassLoader;
  }

  public void setContextClassLoader(ClassLoader cl) {
    this.contextClassLoader = cl;
  }
  ...
}


contextClassLoader「スレッドコンテキストクラスローダー」、これは正確には何ですか?

まず、contextClassLoaderは、明示的に使用する必要がある種類のクラスローダーです。明示的に使用しないと、どこでも使用することはありません。あなたはそれの使用法を示すために次の方法を使うことができます

Thread.currentThread().getContextClassLoader().loadClass(name);


これは、forName(string name)メソッドを使用してターゲットクラスをロードする場合、contextClassLoaderを自動的に使用しないことを意味します。コードの依存関係のために遅延ロードされるクラスは、contextClassLoaderを使用して自動的にロードされません。

次に、スレッドのcontextClassLoaderは親スレッドから継承されます。いわゆる親スレッドは、現在のスレッドを作成したスレッドです。プログラム起動時のメインスレッドのcontextClassLoaderはAppClassLoaderです。これは、手動設定がない場合、すべてのスレッドのcontextClassLoaderがAppClassLoaderであることを意味します。

では、このcontextClassLoaderは正確には何に使用されますか?その目的を説明するために、前述のクラスローダーの分割と協力の原則を使用する必要があります。

同じcontextClassLoaderを共有している限り、スレッド間でクラスを共有できます。contextClassLoaderは親スレッドと子スレッドの間で自動的に渡されるため、共有は自動化されます。

異なるスレッドが異なるcontextClassLoaderを使用する場合、異なるスレッドによって使用されるクラスを分離できます。

ビジネスを分割すると、さまざまなビジネスがさまざまなスレッドプールを使用し、同じcontextClassLoaderがスレッドプール内で共有され、スレッドプール間でさまざまなcontextClassLoaderが使用されます。これにより、分離保護に役立ち、クラスバージョンの競合を回避できます。

contextClassLoaderをカスタマイズしない場合、すべてのスレッドがデフォルトでAppClassLoaderを使用し、すべてのクラスが共有されます。

 

スレッドcontextClassLoaderはまれに使用されます。上記のロジックがあいまいな場合でも、あまり心配する必要はありません。

JDK9がモジュール機能を追加した後、クラスローダーの構造設計はある程度変更されましたが、クラスローダーの原理は同じです。クラスのコンテナーとして、クラスの分離の役割を果たし、依存する必要があります。親委任メカニズムについて。異なるクラスローダー間の協調関係を確立するため。

参照:

1.古くて難しいJavaClassLoader、それを完全に理解する時が来ました 

2.クラスローダの詳細説明

3. ClassLoader(jdk1.8)の動作メカニズムを深く理解する

おすすめ

転載: blog.csdn.net/zhaofuqiangmycomm/article/details/113825099