Javaのクラスローダー

Javaのクラスローダー

1. クラスローダーの概要

クラスローダーはloading Java classes during runtime dynamically to the JVM(Java仮想マシン)を担当します。 また、これらはJRE(Javaランタイム環境)の一部です。 したがって、JVMは、クラスローダーのおかげでJavaプログラムを実行するために、基盤となるファイルやファイルシステムについて知る必要はありません。

また、これらのJavaクラスは一度にメモリに読み込まれるわけではありませんが、アプリケーションで必要な場合に読み込まれます。 これがクラスローダーが登場する場所です。 これらは、クラスをメモリにロードする役割を果たします。

このチュートリアルでは、さまざまなタイプの組み込みクラスローダー、それらがどのように機能するか、および独自のカスタム実装の概要について説明します。

参考文献:

Javaのメモリリークについて

Javaのメモリリークとは何か、実行時にメモリリークを認識する方法、それらの原因、およびそれらを防ぐための戦略を学びます。

ClassNotFoundException vs NoClassDefFoundError

ClassNotFoundExceptionとNoClassDefFoundErrorの違いについて学びます。

2. 組み込みクラスローダーの種類

簡単な例を使用して、さまざまなクラスローダーを使用してさまざまなクラスがどのようにロードされるかを学習することから始めましょう。

public void printClassLoaders() throws ClassNotFoundException {

    System.out.println("Classloader of this class:"
        + PrintClassLoader.class.getClassLoader());

    System.out.println("Classloader of Logging:"
        + Logging.class.getClassLoader());

    System.out.println("Classloader of ArrayList:"
        + ArrayList.class.getClassLoader());
}

実行すると、上記のメソッドは次を印刷します。

Class loader of this class:[email protected]
Class loader of Logging:[email protected]
Class loader of ArrayList:null

ご覧のとおり、ここには3つの異なるクラスローダーがあります。アプリケーション、拡張機能、およびブートストラップ(nullとして表示)。

アプリケーションクラスローダーは、サンプルメソッドが含まれるクラスをロードします。 An application or system class loader loads our own files in the classpath.

次に、拡張機能1はLoggingクラスをロードします。 Extension class loaders load classes that are an extension of the standard core Java classes.

最後に、ブートストラップ1はArrayListクラスをロードします。 A bootstrap or primordial class loader is the parent of all the others.

ただし、最後の出力であるArrayListについては、出力にnullが表示されていることがわかります。 This is because the bootstrap class loader is written in native code, not Java – so it doesn’t show up as a Java class.このため、ブートストラップクラスローダーの動作はJVM間で異なります。

次に、これらの各クラスローダーについて詳しく説明します。

2.1. ブートストラップクラスローダー

[.s1] #Javaクラスはjava.lang.ClassLoaderのインスタンスによってロードされます。 ただし、クラスローダーはそれ自体がクラスです。 したがって、問題は、誰がjava.lang.ClassLoader自体をロードするか?

ここで、ブートストラップまたは原始クラスローダーが登場します。

[.s1]#主にJDK内部クラス(通常はrt.jarおよび$JAVA_HOME/jre/lib directoryにあるその他のコアライブラリ)のロードを担当します。 さらに、Bootstrap class loader serves as a parent of all the other ClassLoader instances。 #

上記の例で指摘したように、This bootstrap class loader is part of the core JVM and is written in native code。 プラットフォームが異なれば、この特定のクラスローダーの実装も異なる可能性があります。

2.2. 拡張クラスローダー

プラットフォームで実行されているすべてのアプリケーションで使用できるようにするためのextension class loader is a child of the bootstrap class loader and takes care of loading the extensions of the standard core Java classes

拡張クラスローダーは、JDK拡張ディレクトリ(通常は$JAVA_HOME/lib/extディレクトリまたはjava.ext.dirsシステムプロパティに記載されているその他のディレクトリ)からロードされます。

2.3. システムクラスローダー

一方、システムまたはアプリケーションクラスローダーは、すべてのアプリケーションレベルクラスをJVMにロードします。 It loads files found in the classpath environment variable, -classpath or -cp command line option。 また、Extensionsクラスローダーの子です。

3. クラスローダーはどのように機能しますか?

クラスローダーは、Javaランタイム環境の一部です。 JVMがクラスを要求すると、クラスローダーはクラスを見つけて、完全修飾クラス名を使用してクラス定義をランタイムにロードしようとします。

java.lang.ClassLoader.loadClass() method is responsible for loading the class definition into runtime。 完全修飾名に基づいてクラスをロードしようとします。

[.s1]#クラスがまだロードされていない場合は、親クラスローダーにリクエストを委任します。 このプロセスは再帰的に発生します。 #

最終的に、親クラスローダーがクラスを見つけられない場合、子クラスはjava.net.URLClassLoader.findClass()メソッドを呼び出して、ファイルシステム自体でクラスを探します。

最後の子クラスローダーもクラスをロードできない場合、java.lang.NoClassDefFoundError or java.lang.ClassNotFoundException.をスローします

ClassNotFoundExceptionがスローされたときの出力の例を見てみましょう。

java.lang.ClassNotFoundException: com.example.classloader.SampleClassLoader
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)

java.lang.Class.forName()を呼び出してすぐに一連のイベントを実行すると、最初に親クラスローダーを介してクラスをロードし、次にjava.net.URLClassLoader.findClass()を使用してクラス自体を検索しようとしていることがわかります。

それでもクラスが見つからない場合は、ClassNotFoundException.をスローします。

クラスローダーには3つの重要な機能があります。

3.1. 委任モデル

[.s1] #Classローダーは、on request to find a class or resource, a ClassLoader instance will delegate the search of the class or resource to the parent class loaderの委任モデルに従います。 #

[.s1]#アプリケーションクラスをJVMにロードするリクエストがあるとしましょう。 システムクラスローダーは、最初にそのクラスのロードをその親拡張クラスローダーに委任し、次に親拡張クラスローダーがそれをブートストラップクラスローダーに委任します。 #

ブートストラップ、次に拡張クラスローダーがクラスのロードに失敗した場合にのみ、システムクラスローダーはクラス自体をロードしようとします。

3.2. ユニークなクラス

[.s1]#委任モデルの結果として、unique classes as we always try to delegate upwardsを確保するのは簡単です。 #

[.s1]#親クラスローダーがクラスを見つけられない場合にのみ、現在のインスタンスがそれ自体を見つけようとします。 #

3.3. 視認性

[.s1]#さらに、children class loaders are visible to classes loaded by its parent class loaders。 #

[.s1]#たとえば、システムクラスローダーによってロードされたクラスは、拡張機能とBootstrapクラスローダーによってロードされたクラスを可視化しますが、その逆はありません。 #

[.s1]#これを説明するために、クラスAがアプリケーションクラスローダーによってロードされ、クラスBが拡張クラスローダーによってロードされる場合、アプリケーションクラスローダーによってロードされる他のクラスに関する限り、AクラスとBクラスの両方が表示されます。 。 #

それにもかかわらず、クラスBは、拡張クラスローダーによってロードされる他のクラスに関する限り、表示される唯一のクラスです。

4. カスタムClassLoader

ファイルが既にファイルシステムにあるほとんどの場合、組み込みのクラスローダーで十分です。

ただし、ローカルハードドライブまたはネットワークからクラスをロードする必要があるシナリオでは、カスタムクラスローダーを使用する必要がある場合があります。

このセクションでは、カスタムクラスローダーのその他のユースケースをいくつか取り上げ、その作成方法を示します。

4.1. カスタムクラスローダーのユースケース

カスタムクラスローダーは、実行時にクラスをロードするだけでなく、次のようなユースケースにも役立ちます。

  1. 既存のバイトコードの変更を支援します。 製織剤

  2. ユーザーのニーズに動的に適したクラスを作成します。 e.g in JDBC, switching between different driver implementations is done through dynamic class loading.

  3. 同じ名前とパッケージのクラスに異なるバイトコードをロードしながら、クラスのバージョン管理メカニズムを実装します。 これは、URLクラスローダー(URL経由でjarをロード)またはカスタムクラスローダーのいずれかを使用して実行できます。

カスタムクラスローダーが役立つ可能性のある、より具体的な例があります。

Browsers, for instance, use a custom class loader to load executable content from a website.ブラウザは、個別のクラスローダーを使用してさまざまなWebページからアプレットをロードできます。 アプレットの実行に使用されるアプレットビューアには、ローカルファイルシステムを調べる代わりに、リモートサーバー上のWebサイトにアクセスするClassLoaderが含まれています。

そして、生のバイトコードファイルをHTTP経由でロードし、JVM内のクラスに変換します。 これらのapplets have the same name, they are considered as different components if loaded by different class loadersであっても。

カスタムクラスローダーが関連する理由を理解したので、ClassLoaderのサブクラスを実装して、JVMがクラスをロードする方法の機能を拡張および要約しましょう。

4.2. カスタムクラスローダーの作成

説明のために、カスタムクラスローダーを使用してファイルからクラスをロードする必要があるとします。

ClassLoaderクラスを拡張し、findClass()メソッドをオーバーライドする必要があります。

public class CustomClassLoader extends ClassLoader {

    @Override
    public Class findClass(String name) throws ClassNotFoundException {
        byte[] b = loadClassFromFile(name);
        return defineClass(name, b, 0, b.length);
    }

    private byte[] loadClassFromFile(String fileName)  {
        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(
                fileName.replace('.', File.separatorChar) + ".class");
        byte[] buffer;
        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        int nextValue = 0;
        try {
            while ( (nextValue = inputStream.read()) != -1 ) {
                byteStream.write(nextValue);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        buffer = byteStream.toByteArray();
        return buffer;
    }
}

上記の例では、デフォルトのクラスローダーを拡張し、指定されたファイルからバイト配列をロードするカスタムクラスローダーを定義しました。

5. java.lang.ClassLoaderを理解する

java.lang.ClassLoaderクラスのいくつかの重要なメソッドについて説明し、それがどのように機能するかをより明確に把握しましょう。

5.1. loadClass()メソッド

public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {

このメソッドは、nameパラメーターを指定してクラスをロードします。 nameパラメーターは、完全修飾クラス名を参照します。

Java仮想マシンはloadClass()メソッドを呼び出して、クラス参照を解決し、resolveをtrueに設定します。 ただし、クラスを解決する必要は必ずしもありません。 If we only need to determine if the class exists or not, then resolve parameter is set to false.

このメソッドは、クラスローダーのエントリポイントとして機能します。

java.lang.ClassLoader:のソースコードからloadClass()メソッドの内部動作を理解することができます

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.
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

メソッドのデフォルト実装は、次の順序でクラスを検索します。

  1. findLoadedClass(String)メソッドを呼び出して、クラスがすでにロードされているかどうかを確認します。

  2. 親クラスローダーでloadClass(String)メソッドを呼び出します。

  3. findClass(String)メソッドを呼び出して、クラスを見つけます。

5.2. defineClass()メソッド

protected final Class defineClass(
  String name, byte[] b, int off, int len) throws ClassFormatError

このメソッドは、バイトの配列をクラスのインスタンスに変換します。 そして、クラスを使用する前に、それを解決する必要があります。

データに有効なクラスが含まれていなかった場合、ClassFormatError.がスローされます

また、finalとしてマークされているため、このメソッドをオーバーライドすることはできません。

5.3. findClass()メソッド

protected Class findClass(
  String name) throws ClassNotFoundException

このメソッドは、パラメーターとして完全修飾名を持つクラスを見つけます。 クラスをロードするための委任モデルに従うカスタムクラスローダーの実装では、このメソッドをオーバーライドする必要があります。

また、親クラスローダーが要求されたクラスを見つけられなかった場合、loadClass()はこのメソッドを呼び出します。

クラスローダーの親がクラスを見つけられない場合、デフォルトの実装はClassNotFoundExceptionをスローします。

5.4. getParent()メソッド

public final ClassLoader getParent()

このメソッドは、委任の親クラスローダーを返します。

セクション2で見たような実装もあります。 nullを使用して、ブートストラップクラスローダーを表します。

5.5. getResource()メソッド

public URL getResource(String name)

このメソッドは、指定された名前のリソースを見つけようとします。

まず、リソースの親クラスローダーに委任します。 If the parent is null, the path of the class loader built into the virtual machine is searched.

それが失敗した場合、メソッドはfindResource(String)を呼び出してリソースを検索します。 入力として指定されたリソース名は、クラスパスに対して相対的または絶対的です。

リソースを読み取るためのURLオブジェクトを返します。リソースが見つからなかった場合、または呼び出し元にリソースを返すための適切な権限がない場合はnullを返します。

Javaはクラスパスからリソースをロードすることに注意することが重要です。

最後に、resource loading in Java is considered location-independentは、リソースを見つけるための環境が設定されている限り、コードがどこで実行されているかは関係ありません。

6. コンテキストクラスローダー

一般に、コンテキストクラスローダーは、J2SEで導入されたクラスロード委任スキームの代替メソッドを提供します。

以前に学んだように、classloaders in a JVM follow a hierarchical model such that every class loader has a single parent with the exception of the bootstrap class loader.

ただし、JVMコアクラスがアプリケーション開発者によって提供されたクラスまたはリソースを動的にロードする必要がある場合、問題が発生する場合があります。

たとえば、JNDIでは、コア機能はrt.jar.のブートストラップクラスによって実装されますが、これらのJNDIクラスは、独立したベンダーによって実装された(アプリケーションクラスパスにデプロイされた)JNDIプロバイダーをロードする場合があります。 このシナリオでは、ブートストラップクラスローダー(親クラスローダー)を呼び出して、アプリケーションローダー(子クラスローダー)から見えるクラスをロードします。

J2SE委任はここでは機能しません。この問題を回避するには、クラスをロードする別の方法を見つける必要があります。 そして、それはスレッドコンテキストローダーを使用して実現できます。

java.lang.ThreadクラスにはメソッドgetContextClassLoader() that returns the ContextClassLoader for the particular threadがあります。 ContextClassLoaderは、リソースとクラスをロードするときにスレッドの作成者によって提供されます。

値が設定されていない場合、デフォルトで親スレッドのクラスローダーコンテキストになります。

7. 結論

クラスローダーは、Javaプログラムを実行するために不可欠です。 この記事の一部として、優れた紹介を提供しました。

ブートストラップ、拡張機能、システムクラスローダーなど、さまざまなタイプのクラスローダーについて説明しました。 Bootstrapはそれらすべての親として機能し、JDK内部クラスのロードを担当します。 一方、拡張機能とシステムは、それぞれJava拡張機能ディレクトリとクラスパスからクラスをロードします。

次に、クラスローダーの動作について説明し、委任、可視性、一意性などの機能について説明した後、カスタムローダーの作成方法について簡単に説明しました。 最後に、Contextクラスローダーの概要を説明しました。

いつものように、コードサンプルはover on GitHubで見つけることができます。