2011/05/30

Java SE 7で追加されたURLClassLoader.close()について

Java SE 7で、java.net.URLClassLoaderクラスにcloseメソッドが追加されました。

closeメソッドが追加された背景については、「Closing a URLClassLoader」 に説明があります。

説明を解釈すると、次のようになります。
JavaアプリケーションがクラスおよびURLClassLoaderオブジェクトへの参照がなくなった後に、ガーベジコレクションおよびファイナライゼーションにより、URLClassLoaderオブジェクトが掴んでいたjarファイルがクローズされます。jarファイルがクローズされた後に、jarファイルを置き換えて再度URLClassLoaderオブジェクトを生成すれば、最新のクラスをロードし直せるようになります。

ところが、jarファイルがクローズされるタイミングというのは、ガーベジコレクションとファイナライゼーションの実行タイミングに依存する、つまり予測できないわけです。特にWindowsではクローズされていないファイルを削除することができないため、jarファイルを置き換えるタイミングが難しいという問題があります。(Linuxでも同じ気がするが…)

そこでJava SE 7では、URLClassLoaderにcloseメソッドを追加し、明示的にjarファイルをクローズできるようにした…
ということのようです。

ここで注意したいのは、APIリファレンスに説明があるのですが、URLClassLoaderオブジェクトが一度ロードしたクラスはcloseメソッドを呼んでも継続して当該クラスを取得可能ですが、まだロードされていないクラスはcloseメソッドを呼ぶとロードされなくなるという点です。

うーん、今まで問題なく動作していたJavaアプリケーションにcloseメソッドを呼ぶ処理を追加したら、ClassNotFoundExceptionがスローされる可能性が出てくるわけですよね?
URLClassLoader.close()の使いどころが、工夫次第でなかなか難しい気がするのですがが、皆さんはどう考えるでしょうか?

Java VMの起動方法

入門的な内容ですが、自分が使おうとしているJavaアプリ/JavaツールがどのようにしてJava VMを起動しているのかが判らないとうまく動作しない場合があります。

Java VMの起動方法には、概ね、次の方法があります。
  1. 環境変数PATHに設定されているパスからjavaコマンドを叩く
  2. 環境変数JAVA_HOMEに設定されているJDK/JREへのパスからjavaコマンドを叩くか、直接jvm.dllにアクセスする
  3. Windowsインストール先\SYSTEM32\java.exeを叩く (Windowsの場合)
  4. Windowsレジストリを参照して、java.exeを叩くか、直接jvm.dllにアクセスする
プラットフォーム非依存のJavaアプリ/Javaツールの場合、上の1または2の方法で起動することが多いでしょう。Windowsでしか動作しないJavaアプリ/Javaツールの場合は、3または4の方法で起動する場合もあります。

たとえば、Apache Antの場合はどうでしょうか?これはプラットフォーム非依存のJavaツールです。
AntはまずJAVA_HOMEを参照してJava VMを起動します。もしJAVA_HOMEが設定されていないか、不適切なパスが設定されている場合、環境変数PATHを参照してJava VMを起動します。

次に留意したいのはクラスパスの設定ですね。Javaアプリ/Javaツールがどのようにクラスパスを決定しているのかを理解する必要があります。クラスパスの設定方法はJavaアプリ/Javaツールによって異なります。

普通にjavaコマンドを叩いてJavaアプリ/Javaツールを起動する場合は、javaコマンドの-cpまたは-classpathオプションでクラスパスを指定するか、あるいは、環境変数CLASSPATHなどで指定するかを使いわける必要があります。-cpと-classpathオプションと環境変数CLASSPATHの相違点は、JDK ドキュメント(JDK Documentation)に説明があります。

独自の定義ファイル/設定ファイルでクラスパスを指定するJavaアプリ/Javaツールも結構あります。特に、
  1. Javaアプリ/Javaツール自体を起動するためのクラスパスと
  2. Javaアプリ/Javaツールを起動後さらに開発(コンパイル)/Javaプログラム起動を行うクラスパス
は別のものである場合が多いです。
たとえばEclispeはプロジェクトごとに異なるクラスパスを設定します。またApache Antのjavaタスクやjavacタスクなどではタスクごとにクラスパスを設定します。

TomcatやGlassFishなどのJavaコンテナやアプリケーションサーバでは、特定ディレクトリに入れたjarファイルが「共有ライブラリ」とみなされ、クラスパスが通ったかのように(言い方が微妙ですが)、動作します。

このようにJava VMがどのように起動されるか、クラスパスがどのように起動されるかを、説明書(readmeなど)で確認し、整理しておきましょう。これが整理できていないと、1つのマシン上で複数のJavaアプリ/Javaツールを上手に起動させることができなくなります。