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が親でロード済みのクラスをリロードしないことを保証します。クラスロードが関わるNoClassDefFoundErrorとjava.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
を使用して実装されています。
上記の図ではBootstrap, Extension, Application ClassLoaderがロードするクラスファイルのロケーションを要約して載せています。
- Bootstrap ClassLoader - JRE/lib/rt.jar
- Extension ClassLoader - JRE/lib/extもしくはjava.ext.dirsが示すディレクトリ
- Application ClassLoader - CLASSPATH環境変数・-classpathもしくは-cpオプション・JARファイル内のマニフェストのClass-Path属性。
How ClassLoader works in Java
上述のようにJavaのClassLoaderは三つの基本原則、delegation
, visibility
, uniqueness
、で動作します。このセクションでは例を交えてJavaのClassLoaderの動作理解とその詳細について見ていきます。まず、以下の図はClassLoaderがdelegationにおいてどのようにクラスロードしているか、を解説したものです。
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
JavaはClass.forName(classname)
とClass.forName(classname, initialized, classloader)
で明示的にクラスをロードするAPIを提供しています。JDBCを使うコード、OracleデータベースにJavaプログラムから接続で見たような、JDBCドライバをロードするのにこれらのAPIが使われます。上述の例のように、ロード対象クラスのバイナリ名と、ロードに使用するClassLoaderを渡せます。java.lang.ClassLoader
のloadClass()
メソッドを呼ぶことでクラスはロードされます。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と思われる