コンテキストクラスローダって何だ?

システムクラスローダと似たようなものに、「(スレッドの) コンテキストクラスローダ (context class loader)」というものがある。前々から名前だけは知っていたのだが、特に必要とする機会もなかったので何なのかは知らないままだった。今回、システムクラスローダが何なのかを調べたついでにこちらも調べたので、これも忘れないように書いておく。

そのスレッドで使われるクラスローダ、ではない

時折見かける間違いとして、「スレッドのコンテキストクラスローダは、そのスレッド内で発生するクラスやリソースのロード要求を処理する」というものがある:

クラスローダサーブレット-TECHSCORE-
ところでアプリケーションはメインスレッドも含めて、必ず1つ「コンテクスト・クラスローダ」を持ちます。コンテキスト・クラスローダとは、そのスレッド内でクラスをロードする場合に使用されるクラスローダのことです。これを使用することにより、特定のスレッドでは、親スレッドで使用できないクラスをロードできるようになります。コンテキスト・クラスローダは、スレッドを生成する親スレッドの側で設定されます。もし設定されない場合には、親スレッドのコンテキスト・クラスローダが設定されます。コンテキスト・クラスローダの設定や取得は、「java.lang.Thread」の「setContextClassLoader」「getContextClassLoader」メソッドで行います。なお最初に起動されるメインスレッドのコンテキスト・クラスローダは、デフォルトでシステム・クラスローダになります。
Javaの“常識”、“非常識” 第5回 - ITアーキテクト [IT Architect]
暗黙的にクラスのロードが要求された(例えば、キーワードnewでインスタンスが生成された)場合、コンテキスト・クラス・ローダが使用される。コンテキスト・クラス・ローダとは、実行スレッドに結び付けられたもので、スレッドの生成時に設定される。Webアプリケーションの場合、サーブレットJSPファイルが呼び出された時点でのコンテキスト・クラス・ローダは、通常各Webアプリケーションのクラス・ローダ(Tomcatの例で言えば、WebappN クラス・ローダ)である
だが、これは全くの間違いだ。Java 仮想マシン仕様には、はっきりと以下のように書かれている:
5.3 Creation and Loading

Creation of a class or interface C denoted by the name N consists of the construction in the method area of the Java virtual machine (§3.5.4) of an implementation-specific internal representation of C. Class or interface creation is triggered by another class or interface D, which references C through its runtime constant pool. Class or interface creation may also be triggered by D invoking methods in certain Java class libraries (§3.12) such as reflection.

《中略》

The Java virtual machine uses one of three procedures to create class or interface C denoted by N:


  • If N denotes a nonarray class or an interface, one of the two following methods is used to load and thereby create C:
    • If D was defined by the bootstrap class loader, then the bootstrap class loader initiates loading of C (§5.3.1).
    • If D was defined by a user-defined class loader, then that same user-defined class loader initiates loading of C (§5.3.2).
  • Otherwise N denotes an array class. An array class is created directly by the Java virtual machine (§5.3.3), not by a class loader. However, the defining class loader of D is used in the process of creating array class C.
要するに、こういうことだ:

  1. クラスのロード要求はメソッド内にあるはずだ。
  2. そのメソッドは何かのクラスに属しているはずだ。
  3. そのクラスをロードしたクラスローダがあるはずだ。
  4. 要求されているクラスのロードには、そのクラスローダを使う。

スレッドは関係ないし、ましてやコンテキストクラスローダなど名前さえも出てこない。

“スレッドごとのクラスパス”としてのコンテキストクラスローダ

コンテキストクラスローダが具体的に何に使われるのかは、Java 仮想マシン仕様にも Java 言語仕様にも Java プラットフォーム API 仕様にも書かれていない。いろいろとウロウロした結果、『IBM WSDD | クラスローダーとJ2EEパッケージング戦略を理解する - 第5回 - Japan』という文書に行き着いた。これによると、どうやらこういうことらしい:

  1. 特定の名前のクラスをロードして使うユーティリティ、みたいなものを考える。
  2. そういう名前のクラスの実装は、スレッドの側がそれぞれに用意することにする。
  3. だが、普通にやると、そのユーティリティをロードしたクラスローダが使われてしまうので、スレッドごとに定義されたクラスをロードするという目的は果たせない。
  4. クラスローダを引数などで指定するのもありだが、毎回毎回そんなものを指定するのは面倒くさい。
  5. スレッドの属性として、“そういう場合に使うべきクラスローダ”というのを登録できればいい。

言うなれば、“スレッドごとのクラスパス”みたいなものだ。

コンテキストクラスローダは、明示的に使用しなければ使われない

コンテキストクラスローダというのは、単なるスレッドの属性に過ぎない。極端なことを言えば、java.lang.Thread クラスの getContextClassLoader() で取得できることと setContextClassLoader(ClassLoader) で設定できるもの、というのがその全てだ。したがって、コンテキストクラスローダは、以下のように明示的に取得して使用しなければ、決して使われることはない:

final ClassLoader contextClassLoader =
    Thread.currentThread().getContextClassLoader();
  assert contextClassLoader != null;

contextClassLoader.loadClass("SomeClass");

いずれも詳しくないので詳細は分からないが、Java EERMI の中に、このようにコンテキストクラスローダが使われると規定されている箇所があるらしい。