用語は正しく覚えよう: システムクラスローダとブートストラップクラスローダ

ディスカッションの最中、どうも話がかみ合わないと思ったら用語の使い方が間違っていた、ということが時々ある。同じ概念を違う用語で呼んでいる場合はすぐに気づくので問題ない*1が、同じ用語で違う概念を指していると混乱の元凶になる。なので、ちょっと面倒ではあるが、正しい用語を1次情報に当たって確認するクセを付けてほしい。
とまあ偉そうなことを書いたが、僕自身、用語を間違って覚えていて相手を混乱させてしまった経験は枚挙に暇がない。今回もまた1つそんなことがあったので、備忘のためにここに書いておく。
間違って覚えていた用語は「システムクラスローダ (system class loader)」。java.lang.ClassLoader#getSystemClassLoader() で取得できるクラスローダのことだが、実は、JVM が内部的に持っているクラスローディング機構((JVM には、ClassLoader オブジェクトを使わずにクラスをロードする特別の仕組みがある。そうでないと、たとえば、「ClassLoader クラスをロードする ClassLoader オブジェクト」といった鶏と卵の問題が生じてしまう。))のことなんだと思っていた。

JVM 自体が備えるクラスローディング機構は「ブートストラップクラスローダ」

実際には、JVM が持つ特別のクラスローディング機構を指す用語は「ブートストラップクラスローダ (bootstrap class loader)」。これは、Java 仮想マシン仕様*2の以下の記述による:

5.3 Creation and Loading
There are two types of class loaders: user-defined class loaders and the bootstrap class loader supplied by the Java virtual machine. Every user-defined class loader is an instance of a subclass of the abstract class ClassLoader.
ブートストラップクラスローダは特別な存在で、Java プログラムからこれにアクセスする手段は存在しない。たとえば、ブートストラップクラスローダによってロードされたクラスの Class#getClassLoader() メソッドを呼び出しても、null が返されてしまう。

結局、「システムクラスローダ」とは何なのか?

では、「システムクラスローダ」というのはいったい何か? 実は、この用語は、Java 仮想マシン仕様にも Java 言語仕様*3にも現れない。ClassLoader#getSystemClassLoader() メソッドの仕様に以下のようにあるだけだ:


Returns the system class loader for delegation. This is the default delegation parent for new ClassLoader instances, and is typically the class loader used to start the application.
そう。「システムクラスローダ」とは、「クラスローダの委譲連鎖におけるデフォルトの親」のことだ。それ以上でもそれ以下でもない。通常は main メソッドのクラスをロードするが、それも「典型的には (typically)」そうなだけで必ずそうであるとは限らない。実際、Java 仮想マシン仕様には以下のような記述もある:
5.2 Virtual Machine Start-up

The Java virtual machine starts up by creating an initial class, which is specified in an implementation-dependent manner, using the bootstrap class loader (§5.3.1). The Java virtual machine then links the initial class, initializes it, and invokes its public class method void main(String[]).《中略》

In some implementations of the Java virtual machine the initial class could be provided as a command line argument, as in JDK releases 1.0 and 1.1. Alternatively, the initial class could be provided by the implementation. In this case the initial class might set up a class loader that would in turn load an application, as in the Java 2 SDK, Standard Edition, v1.2. Other choices of the initial class are possible so long as they are consistent with the specification given in the previous paragraph.

どうやら、JDK 1.0 や 1.1 の頃は main メソッドのクラスをブートストラップクラスローダがロードしていたらしい。
ちなみに、ClassLoader#getSystemClassLoader() メソッドの仕様には、システムクラスローダは好きなクラスに差し替えることができると書いてある:


The default system class loader is an implementation-dependent instance of this class.


If the system property "java.system.class.loader" is defined when this method is first invoked then the value of that property is taken to be the name of a class that will be returned as the system class loader. The class is loaded using the default system class loader and must define a public constructor that takes a single parameter of type ClassLoader which is used as the delegation parent. An instance is then created using this constructor with the default system class loader as the parameter. The resulting class loader is defined to be the system class loader.

これからすると、「システムクラスローダ」というのは、特定のクラスローダを指す用語ではなく、ある種のクラスローダが占める役割を指す用語だと解釈するのが自然かもしれない。

*1:分からない言葉が出てきたら、その場ですぐに聞き返すクセを付けよう。

*2:『The Java Virtual Machine Specification Second Edition』

*3:『The Java Language Specification, Third Edition』

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

システムクラスローダと似たようなものに、「(スレッドの) コンテキストクラスローダ (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 の中に、このようにコンテキストクラスローダが使われると規定されている箇所があるらしい。