kagamihogeの日記

kagamihogeの日記です。

How ClassLoader Works in Java をテキトーに訳した

http://javarevisited.blogspot.jp/2012/12/how-classloader-works-in-java.html をテキトーに訳した。

How ClassLoader Works in Java

Javaのクラスローダーは実行時にクラスをロードするのに使われます。JavaのClassLoaderは三つの基本原則に基づいて動作し、それぞれdelegation, visibility, uniquenessです。Delegation原則は親クラスローダーにクラスロードのリクエストを委譲し、もし親クラスローダーがクラスを発見あるいはロード出来ない場合のみクラスをロードします。Visibility原則は、子クラスローダーは親クラスローダーがロードする全クラスを参照可能ではあるが、親クラスローダーは子クラスローダーがロードするクラスを参照出来ません。Uniqueness原則は、あるクラスが一度だけロードされることで、基本的にはdelegationで実現し、子ClassLoaderが親でロード済みのクラスをリロードしないことを保証します。クラスロードが関わるNoClassDefFoundErrorjava.lang.ClassNotFoundExceptionの問題解決にはクラスローダーの正確な理解が必須です。また、高度なJava知識を要求する採用面接でClassLoaderは頻出トピックで、JavaのClassLoaderの動作に関する詳細な知識やJavaにおけるclasspathの動作の理解が求められます。各種のJavaの採用面接において、Javaで二つの異なるClassLoaderに一つのクラスをロードすることは可能か?といった質問を見たことがあります。このJavaプログラミングチュートリアルでは、JavaのClassLoaderとは何なのかと、Java ClassLoaderの詳細とその動作について見ていきます。

What is ClassLoader in Java

JavaのClassLoaderクラスはクラスファイルのロードに使われます。

javacコンパイラJavaのコードをクラスファイルにコンパイルし、JVMはクラスファイルのバイトコードを実行することにより、Javaプログラムを動かします。ClassLoaderの役割は、ファイルシステム・ネットワーク・その他のソースから、クラスファイルをロードすることです。Javaには三つのデフォルトクラスローダーがあります。Bootstrap, Extension, SystemないしApplicationクラスローダーです。いずれのクラスローダーも定義済みロケーションを持ち、そこからクラスファイルをロードします。Bootstrap ClassLoaderの役割はrt.jarから標準JDKクラスファイルをロードすることで、また、Javaのすべてのクラスローダーの親となります。Bootstrapクラスローダーには親がありません。String.class.getClassLoader()を呼ぶとnullを返すので、これに基づくコードはNullPointerExceptionをスローします。なお、BootstrapクラスローダーはPrimordial ClassLoaderでもあります*1。Extension ClassLoaderはその親であるBootstrapにロードのリクエストを委譲し、もしそのリクエストが失敗した場合は、jre/lib/extディレクトリもしくはjava.ext.dirsシステムプロパティが示す他のディレクトリからクラスをロードします。JVM内のExtension ClassLoaderはsun.misc.Launcher$ExtClassLoaderで実装されています。JavaのクラスロードにJVMが使用する三番目のクラスローダーはSystemないしApplicationクラスローダーで、その役割は、CLASSPATH環境変数-classpathもしくは-cpコマンドラインオプション・JAR内のマニフェストファイルのClass-Path属性から、アプリケーション固有のクラスをロードすることです。ApplicationクラスローダーはExtension ClassLoaderの子で、sun.misc.Launcher$AppClassLoaderで実装されています。BootstrapクラスローダーがCなどのネイティブ言語で実装されているのを除き、すべてのJavaクラスローダーはjava.lang.ClassLoaderを使用して実装されています。

java_classloader_hierarchy.PNG

上記の図ではBootstrap, Extension, Application ClassLoaderがロードするクラスファイルのロケーションを要約して載せています。

  1. Bootstrap ClassLoader - JRE/lib/rt.jar
  2. Extension ClassLoader - JRE/lib/extもしくはjava.ext.dirsが示すディレクトリ
  3. Application ClassLoader - CLASSPATH環境変数・-classpathもしくは-cpオプション・JARファイル内のマニフェストのClass-Path属性。

How ClassLoader works in Java

上述のようにJavaのClassLoaderは三つの基本原則、delegation, visibility, uniqueness、で動作します。このセクションでは例を交えてJavaのClassLoaderの動作理解とその詳細について見ていきます。まず、以下の図はClassLoaderがdelegationにおいてどのようにクラスロードしているか、を解説したものです。

Java_classloader_working.PNG

Delegation principles

Javaのクラスロードおよび初期化のタイミングで解説したように、クラスは必要に応じてロードされます。いま、Abc.classというアプリケーション固有のクラスがあるとして、このクラスの初回ロードリクエストがApplication ClassLoaderに来るとその親のExtension ClassLoaderに委譲し、更にそこからPrimordialないしBootstrapクラスローダーに委譲が行われます。Primordialはrt.jarからAbc.classを検索して、そこに存在しないので、リクエストはExtensionクラスローダーに移行してjre/lib/extディレクトリを検索し、もしクラスがそこに存在すればExtensionクラスローダーはそのクラスをロードして、Applicationクラスローダーはロードを行いません。ただし、Extensionクラスローダーがロード出来ない場合は、Applicationクラスローダーがクラスパスからロードします。なお、Classpathはクラスファイルのロードに使われるもので、PATHはjavacやjavaコマンドなど実行可能ファイルの場所を指すのに使われます。

Visibility Principle

visibility基本原則では、子ClassLoaderは親ClassLoaderがロードしたクラスを参照可能ですが、その逆は出来ません。この意味は、ApplicationクラスローダーがAbcクラスをロード後*2、extensionクラスローダで明示的にAbcをロードしようとすると、java.lang.ClassNotFoundExceptionをスローします。コード例は以下です。

 package test;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Java program to demonstrate How ClassLoader works in Java,
 * in particular about visibility principle of ClassLoader.
 *
 * @author Javin Paul
 */

public class ClassLoaderTest {
 
    public static void main(String args[]) {
        try {         
            //printing ClassLoader of this class
            System.out.println("ClassLoaderTest.getClass().getClassLoader() : "
                                 + ClassLoaderTest.class.getClassLoader());

         
            //trying to explicitly load this class again using Extension class loader
            Class.forName("test.ClassLoaderTest", true 
                            ,  ClassLoaderTest.class.getClassLoader().getParent());
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(ClassLoaderTest.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

}

Output:
ClassLoaderTest.getClass().getClassLoader() : sun.misc.Launcher$AppClassLoader@601bb1
16/08/2012 2:43:48 AM test.ClassLoaderTest main
SEVERE: null
java.lang.ClassNotFoundException: test.ClassLoaderTest
        at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
        at sun.misc.Launcher$ExtClassLoader.findClass(Launcher.java:229)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:247)
        at test.ClassLoaderTest.main(ClassLoaderTest.java:29)

Uniqueness Principle

Uniqueness基本原則では、親がロードしたクラスを子で再度ロードすべきではない、というものです。DelegationとUniqueness基本原則を守らずに自分自身でクラスロードするクラスローダーを書くことは勿論可能ですが、あまり有益とは言えません。自前のClassLoaderを書く場合にはすべてのクラスローダー基本原則を守るべきです。

How to load class explicitly in Java

JavaClass.forName(classname)Class.forName(classname, initialized, classloader)で明示的にクラスをロードするAPIを提供しています。JDBCを使うコード、OracleデータベースにJavaプログラムから接続で見たような、JDBCドライバをロードするのにこれらのAPIが使われます。上述の例のように、ロード対象クラスのバイナリ名と、ロードに使用するClassLoaderを渡せます。java.lang.ClassLoaderloadClass()メソッドを呼ぶことでクラスはロードされます。java.lang.ClassLoaderは対象クラスのバイトコードの検索にfindClass()メソッドを呼びます。先の例ではExtension ClassLoaderは、クラスファイルとJARのリソースとディレクトリを、検索するのにjava.net.URLClassLoaderを使います。"/"で終わる検索パスはいずれもディレクトリと見なされます。findClass()がクラスを発見出来ない場合はjava.lang.ClassNotFoundExceptionをスローし、発見した場合はdefineClass()バイトコードを.classインスタンスに変換して呼び出し元へ返します。

Where to use ClassLoader in Java

JavaのClassLoaderは強力な概念であり様々な場所で使われています。ClassLoaderのよくある例の一つはAppletがクラスロードに使うAppletClassLoaderです。Appletsはローカルファイルシステムではなく通常インターネットからロードされるため、別のClassLoaderを使うことで複数のソースから同一クラスをロード可能で、それら複数のソースはJVM内では異なるクラスとして扱われます。J2EEはそれぞれの配置場所からクラスをロードするのにクラスローダーを使います。WARファイルはWeb-app ClassLoaderがロードし、EJB-JARにバンドルするクラスは別のクラスローダーがロードします。また、ある種のwebサーバはClassLoaderを使用してホットデプロイ機能を実現しています。データベースやその他の永続化機構からクラスをロードするのにもClassLoaderを使えます。

以上がJavaのClassLoaderとは何かおよびJavaのClassLoaderの動作となります。JavaのClassLoader絡みの問題解決やデバッグにdelegation, visibility, uniquenessが極めて重要なことを見てきました。ClassLoaderの動作概要の知識はJavaアプリケーションの設計とパッケージングを行う開発者とアーキテクトにとって必須事項です。

*1:Primordialは1.原初の、最初から存在する 2.根源的な、根本となる http://eow.alc.co.jp/search?q=Primordial

*2:原文はthanだが文脈的にthenと思われる