クラスローダーとクラスの親の委任メカニズムの詳細な説明

クラスローダー

クラスローダーとは

仮想マシン設計チームは、「クラスの完全修飾名を通じてこのクラスを記述するバイナリバイトストリームを取得する」というアクションをJava仮想マシンの外部のクラスロードステージに置き、アプリケーションがすべてを取得する方法を決定できるようにします必要なクラス。このアクションを実装するコードモジュールは、「クラスローダー」と呼ばれます。

クラスローダーの階層

image-20200827154815302

親の委任モデルでは、トップレベルのスタートアップクラスローダーを除き、他のすべてのクラスローダーに独自の親クラスローダーが必要です。ただし、ここでのクラスローダー間の親子関係は、通常、継承関係では実現されませんが、通常、構成関係は親ローダーのコードを再利用するために使用されます。

Java仮想マシンの観点から見ると、クラスローダーは2つしかありません。1つはC ++言語で実装され、仮想マシン自体の一部であるBootstrap ClassLoader(Bootstrap ClassLoader)です。それは他のすべてのクラスローダーであり、これらのクラスローダーは仮想マシンとは関係なくJava言語によって実装され、すべて抽象クラスjava.lang.ClassLoaderから継承します。

Java開発者の観点からは、クラスローダーをより詳細に分割できます。ほとんどのJavaプログラムは、次の3つのシステムによって提供されるクラスローダーを使用します。

Bootstrap ClassLoader(Bootstrap ClassLoader)

このクラスは、<JAVA_HOME> \ libディレクトリ、または-Xbootclasspathパラメーターで指定されたパスに格納され、仮想マシン(rt.jar、tools.jarなどのファイル名で識別される)によって認識されます。名前が一致しないクラスライブラリは、libディレクトリに配置されてもロードされません)クラスライブラリは、仮想マシンのメモリにロードされます。

拡張ClassLoader

このローダーはsun.misc.Launcher $ ExtClassLoaderによって実装され、<JAVA_HOME> \ lib \ extディレクトリまたはjava.ext.dirsシステム変数で指定されたパスにあるすべてのクラスライブラリをロードします。拡張クラスローダーを使用します。

アプリケーションClassLoader

このクラスローダーは、sun.misc.Launcher $ AppClassLoaderによって実装されます。アプリケーションクラスローダーはClassLoaderクラスのgetSystem-ClassLoader()メソッドの戻り値であるため、「システムクラスローダー」と呼ばれることもあります。

ユーザークラスパス(ClassPath)にあるすべてのクラスライブラリの読み込みを担当し、開発者はこのクラスローダーをコードで直接使用することもできます。アプリケーションで独自のクラスローダーをカスタマイズしていない場合、これは通常、プログラムのデフォルトのクラスローダーです。

私たちのアプリケーションは、これら3つのタイプのローダーによって互いに連携してロードされます。必要に応じて、独自のクラスローダーを追加することもできます。

クラスローディングの3つの方法

  1. コマンドラインからアプリケーションが起動されたときに、JVMによって最初にロードされます
  2. Class.forName()メソッドを使用して動的にロードする
  3. ClassLoader.loadClass()メソッドを使用して動的にロードする

コード例:

public class loaderTest {
    
     
        public static void main(String[] args) throws ClassNotFoundException {
    
     
                ClassLoader loader = HelloWorld.class.getClassLoader(); 
                System.out.println(loader); 
                //使用ClassLoader.loadClass()来加载类,不会执行初始化块 
                loader.loadClass("Test2"); 
                //使用Class.forName()来加载类,默认会执行初始化块 
//                Class.forName("Test2"); 
                //使用Class.forName()来加载类,并指定ClassLoader,初始化时不执行静态块 
//                Class.forName("Test2", false, loader); 
        } 
}

public class Test2 {
    
     
        static {
    
     
                System.out.println("静态初始化块执行了!"); 
        } 
}

Class.forName()和ClassLoader.loadClass()区别

  • Class.forName():jvmの外でクラスの.classファイルをロードし、クラスを説明して、クラスで静的ブロックを実行します。

  • ClassLoader.loadClass():.classファイルをjvmにロードすることは1つだけです。staticのコンテンツは実行されず、staticブロックはnewInstanceでのみ実行されます。

  • Class.forName(name、initialize、loader)関数とパラメーターを使用して、静的ブロックをロードするかどうかを制御することもできます。そして、newInstance()メソッドが呼び出された場合にのみ、クラスのオブジェクトを作成するためにコンストラクターが呼び出されます。

JVMクラスローディングメカニズム

完全に責任がある

クラスローダーがクラスのロードを担当する場合、別のクラスローダーを使用してクラスをロードしない限り、クラスが依存し参照される他のクラスもクラスローダーによってロードされます。

親代理

親クラスローダーが最初にクラスをロードしようとし、親クラスローダーがクラスをロードできない場合にのみ、独自のクラスパスからクラスをロードしようとします。

キャッシングメカニズム

キャッシュメカニズムにより、読み込まれたすべてのクラスが確実にキャッシュされます。クラスをプログラムで使用する必要がある場合、クラスローダーはまずキャッシュ領域からクラスを検索し、キャッシュ領域のみが存在しない場合、システムは対応するクラスを読み取りますバイナリデータはClassオブジェクトに変換され、バッファ領域に格納されます。これが、クラスを変更した後、プログラムの変更を有効にするためにJVMを再起動する必要がある理由です。

親の委任メカニズム

親の委任モデルの作業プロセスは次のとおりです。クラスローダーがクラスの読み込み要求を受け取った場合、最初にそれ自体でクラスの読み込みを試みるのではなく、要求を親のクラスローダーに委任して完了します。クラスの各レベルローダーはこのようなものなので、すべての読み込み要求は、親ローダーが読み込み要求を完了できない(必要なクラスが検索範囲に見つからない)と報告した場合にのみ、最終的にトップレベルのスタートアップクラスローダーに送信されます。その時、子ローダーはそれ自体でロードを完了しようとします。

次に例を示します。

  1. AppClassLoaderがクラスをロードするとき、最初にそれ自体でクラスをロードしようとするのではなく、クラスのロード要求を親クラスローダーExtClassLoaderに委任して完了します。
  2. ExtClassLoaderがクラスをロードするとき、それ自体はクラスをロードしようとしませんが、クラスのロード要求をBootStrapClassLoaderに委任して完了します。
  3. BootStrapClassLoaderがロードに失敗した場合(たとえば、クラスが$ JAVA_HOME / jre / libに見つからない場合)、ExtClassLoaderを使用してロードが試行されます。
  4. ExtClassLoaderもロードに失敗した場合は、AppClassLoaderを使用してロードします。AppClassLoaderもロードに失敗した場合は、例外ClassNotFoundExceptionを報告します。

親の委任メカニズム

image-20200827135115768

上の図に示すクラスローダー間の階層関係は、クラスローダーの親の委任モデルと呼ばれます。親の委任モデルでは、トップレベルのスタートアップクラスローダーに加えて、他のすべてのクラスローダーに独自の親クラスローダーが必要です。ここで、クラスローダー間の親子関係は、通常、継承関係では実現されませんが、親ローダーのコードを再利用するために構成関係が使用されます。

クラスローダーの親委譲モデルはJDK 1.2で導入され、その後ほとんどすべてのJavaプログラムで広く使用されましたが、必須の制約モデルではなく、Java設計者が開発者に推奨するクラスですローダーの実装。

親の委任モデルの作業プロセスは次のとおりです。クラスローダーがクラスの読み込み要求を受け取った場合、最初にそれ自体でクラスの読み込みを試みるのではなく、要求を親のクラスローダーに委任して完了します。クラスの各レベルローダーはこのようなものなので、すべての読み込み要求は、親ローダーが読み込み要求を完了できない(必要なクラスが検索範囲に見つからない)と報告した場合にのみ、最終的にトップレベルのスタートアップクラスローダーに送信されます。その時、子ローダーはそれ自体でロードを完了しようとします。

親委譲モデルを使用してクラスローダー間の関係を整理すると、Javaクラスがクラスローダーと一緒に優先順位のある階層関係を持つという明らかな利点があります。たとえば、rt.jarに格納されているjava.lang.Objectクラスは、どのクラスローダーがこのクラスをロードしたいかに関係なく、最終的にはロードするモデルの上部にあるスタートアップクラスローダーに委任されるため、Objectクラスはプログラム内にあります。さまざまなクラスローダー環境はすべて同じクラスです。逆に、親委任モデルが使用されておらず、各クラスローダーがそれをロードする場合、ユーザーがjava.lang.Objectと呼ばれるクラスを記述してプログラムのClassPathに配置すると、さらに多くの別のObjectクラスを使用すると、Java型システムの最も基本的な動作が保証されず、アプリケーションが混乱します。

保護者委任の利点

  • システムクラスは、メモリ内の同じバイトコードの複数のコピーを防止します
  • Javaプログラムの安全で安定した動作を保証する

image-20200827135310660

親委任コードの実装

public Class<?> loadClass(String name)throws ClassNotFoundException {
    
    
            return loadClass(name, false);
    }
    protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
    
    
            // 首先判断该类型是否已经被加载
            Class c = findLoadedClass(name);
            if (c == null) {
    
    
                //如果没有被加载,就委托给父类加载或者委派给启动类加载器加载
                try {
    
    
                    if (parent != null) {
    
    
                         //如果存在父类加载器,就委派给父类加载器加载
                        c = parent.loadClass(name, false);
                    } else {
    
    
                    //如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name)
                        c = findBootstrapClass0(name);
                    }
                } catch (ClassNotFoundException e) {
    
    
                 // 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
                    c = findClass(name);
                }
            }
            if (resolve) {
    
    
                resolveClass(c);
            }
            return c;
        }
  

このコードのロジックは明確で理解しやすいです。最初に、要求されたタイプがロードされているかどうかを確認します。ロードされていない場合は、親ローダーのloadClass()メソッドを呼び出します。親ローダーが空の場合、デフォルトで起動クラスローダーが親ロードとして使用されます。端末。親クラスローダーがロードに失敗してClassNotFoundExceptionをスローした場合は、独自のfindClass()メソッドを呼び出してロードを試みます。

カスタムクラスローダー

通常、システムクラスローダーを直接使用します。ただし、クラスローダーをカスタマイズする必要がある場合もあります。たとえば、アプリケーションはJavaクラスのバイトコードをネットワーク経由で送信します。セキュリティを確保するために、これらのバイトコードは暗号化されています。現時点では、システムクラスローダーはそれらをロードできないため、カスタムクラスローダーが必要です。成し遂げる。カスタムクラスローダーは、通常、ClassLoaderクラスを継承します。上記のloadClassメソッドの分析から、必要なのはfindClassメソッドを書き換えるだけです。

以下では、例を使用してカスタムクラスローダーのプロセスを示します。

カスタムクラスローダーのコアは、バイトコードファイルを取得することです。これが暗号化されたバイトコードの場合、このクラスでファイルを復号化する必要があります。これは単なるデモンストレーションであるため、クラスファイルを暗号化しなかったため、復号化プロセスはありません。

public class MyClassLoader extends ClassLoader {
    
    

    private String root;

    protected Class<?> findClass(String name) throws ClassNotFoundException {
    
    
        byte[] classData = loadClassData(name);
        if (classData == null) {
    
    
            throw new ClassNotFoundException();
        } else {
    
    
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] loadClassData(String className) {
    
    
        String fileName = root + File.separatorChar
                + className.replace('.', File.separatorChar) + ".class";
        try {
    
    
            InputStream ins = new FileInputStream(fileName);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];
            int length = 0;
            while ((length = ins.read(buffer)) != -1) {
    
    
                baos.write(buffer, 0, length);
            }
            return baos.toByteArray();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        return null;
    }

    public String getRoot() {
    
    
        return root;
    }

    public void setRoot(String root) {
    
    
        this.root = root;
    }

    public static void main(String[] args)  {
    
    

        MyClassLoader classLoader = new MyClassLoader();
        classLoader.setRoot("D:\\temp");

        Class<?> testClass = null;
        try {
    
    
            testClass = classLoader.loadClass("com.pdai.jvm.classloader.Test2");
            Object object = testClass.newInstance();
            System.out.println(object.getClass().getClassLoader());
        } catch (ClassNotFoundException e) {
    
    
            e.printStackTrace();
        } catch (InstantiationException e) {
    
    
            e.printStackTrace();
        } catch (IllegalAccessException e) {
    
    
            e.printStackTrace();
        }
    }
}
  

ここで注意すべきことがいくつかあります

  1. ここで渡されるファイル名は、クラスの完全修飾名、つまりcom.pdai.jvm.classloader.Test2形式である必要があります。defineClassメソッドはこの形式で処理されるためです。
  2. 親の委任モデルを壊すのは簡単なので、loadClassメソッドをオーバーライドしないことをお勧めします。
  3. このタイプのTestクラス自体はAppClassLoaderクラスによってロードできるため、クラスパスにcom / pdai / jvm / classloader / Test2.classを置くことはできません。それ以外の場合は、親の委任メカニズムが存在するため、このクラスは、カスタムクラスローダーではなく、AppClassLoaderによって直接ロードされます。

おすすめ

転載: blog.csdn.net/kaihuishang666/article/details/108262819
おすすめ