Java クラス・ローダーのメモリー・リークの診断

IBM ClassLoader Analyzer

このチュートリアルでは、クラスおよびクラスのロード・メカニズムの基本概念を説明した後、クラス/クラス・ローダーのリークを生成し、IBM ClassLoader Analyzer を使用して Java ヒープ・ダンプ、Java クラス・トレース、IBM Javacore といったさまざまな成果物の問題を診断します。

Jinwoo Hwang, Senior Software Engineer, IBM

Jinwoo Hwang は、ソフトウェア・エンジニア、発明家、著者、そして米国のノースカロライナ州リサーチ・トライアングル・パークにある IBM WebSphere Application Server Technical Support に勤務するテクニカル・リーダーとして、これまで IBM HeapAnalyzer やその他多くの技術を設計、作成してきました。著書には『C Programming for Novices』がある他、さまざまなウェブキャストや出版物で活躍しています。彼は、IBM 認定ソリューション開発者、IBM 認定 WebSphere Application Server システム・アドミニストレーターであるとともに Java プラットフォームの SUN 認定プログラマーの資格を持っており、IBM developerWorks の Contributing Author でもあります。彼が関係する出版物や、ウェブキャスト、製品についての詳細は、JinwooHwang.com にアクセスしてください。



2013年 12月 05日

はじめに

IBM ClassLoader Analyzer へようこそ。このツールは、Java クラス・ローダー・トレース・ファイルや IBM javacore ファイルを基に、Java クラス、Java クラス・ローダー、ライブラリーを分析するためのものです。このチュートリアルでは、まず、クラス・ローダー、クラス・ロード・メカニズム、ポリシーの基本概念を説明します。次にテスト・アプリケーションを作成し、クラス/クラス・ローダーのリークを生成します。そして IBM ClassLoader Analyzer を使用して、Java ヒープ・ダンプ、Java クラス・トレース、IBM Javacore といったさまざまな成果物の問題を診断します。

注: このツールは現存するままの状態で提供され、商品性の保証、特定目的適合性の保証および法律上の瑕疵担保責任を含む、すべての明示もしくは黙示の保証責任または保証条件は適用されないものとします。

前提条件

  • バージョン 6 以上の Java ランタイム環境
  • Java クラス・ローダー・トレース (-verbose:class) ファイル
  • [オプション] IBM Java 仮想マシンから生成された Javacore

機能

  • クラス・ローダー数、クラス数、ロードされたライブラリー数の計算 (javacore を使用)
  • クラス・ローダーでのリークの疑いの自動検出 (javacore を使用)
  • クラスおよびロードされたクラスの合計数を表示するサスペクト・ビュー (javacore を使用)
  • クラス・ローダーとクラスのツリー・ビュー (javacore を使用)
  • ロードされたクラスを表示するクラス・ローダー・ビュー (javacore を使用)
  • クラス・ローダーのライブラリー・ツリー・ビュー (javacore を使用)
  • javacore およびクラス・ローダー・トレースの自動検出
  • 複数回ロードされた、疑いのあるクラスの自動検出 (クラス・ローダー・トレースを使用)
  • 疑いのあるクラスのビュー (クラス・ローダー・トレースを使用)
  • jar およびクラスのツリー・ビュー (クラス・ローダー・トレースを使用)

概要

Java クラス・ローダーとは?

Java ランタイム環境を起動すると、Java 仮想マシンがブートストラップ・クラス、拡張クラス、ユーザー・クラスなどの一連のクラスを見つけてロードします。ブートストラップ・クラスには、Java システム・プラットフォームおよびコアとなる Java ライブラリーを構成するクラスが含まれています。拡張クラスは、コアとなる Java プラットフォームに備わる機能をアプリケーション開発者が拡張するために使用するクラスを集めたオプション・パッケージです。ユーザー・クラスは、アプリケーション開発者が提供する、拡張クラスに分類されないクラスです。これらのクラスは、タイプごとにそれぞれ異なるクラス・ローダーによってロードされます。したがって、クラス・ローダーは、特定のバイナリー・クラス名またはインターフェース名を持つバイナリー形式のクラスおよびインターフェースを探さなければなりません。クラス、インターフェース、リソースを見つけるために、クラス・ローダーは委譲モデルを使用します。つまり、クラス・ローダーは自身でクラスまたはリソースのロードを試みる前に、デフォルトで親のクラス・ローダーにクラスとリソースのロードを委譲します。

Java 仮想マシンには、3 つのクラス・ローダーがあります。具体的には、ブートストラップ・クラスをロードするブートストラップ・クラス・ローダー、拡張クラスをロードする拡張クラス・ローダー、そしてユーザー・クラスをロードするアプリケーション・クラス・ローダーです。これらのクラス・ローダーの間には階層関係があります。アプリケーション・クラス・ローダーの親は拡張クラス・ローダーであり、拡張クラス・ローダーの親はブートストラップ・クラス・ローダーです。ブートストラップ・クラス・ローダーの親に該当するクラス・ローダーはありません。

ブートストラップ・クラス・ローダーは、sun.boot.class.path システム・プロパティーに保管されているブートストラップ・パスからクラスとインターフェースをロードします。ブートストラップ・パスは、System API メソッド getProperty() を使用した以下の Java コードで簡単に見つけることができます。

System.getProperty("sun.boot.class.path");

拡張クラス・ローダーは、拡張ディレクトリーからクラスとインターフェースをロードします。拡張ディレクトリーの場所に関する情報は、java.ext.dirs システム・プロパティーに格納されます。拡張クラス・ローダーは、sun.misc.Launcher$ExtClassLoader クラスによって実装されます。以下の Java コードは、拡張ディレクトリーを返します。

System.getProperty("java.ext.dirs");

アプリケーション・クラス・ローダーは、クラス・パスからクラスとインターフェースをロードします。クラス・パスの場所に関する情報は、java.class.path システム・プロパティーに格納されます。アプリケーション・クラス・ローダーは、sun.misc.Launcher$AppClassLoader クラスによって実装されます。以下の Java コードは、クラス・パスを返します。

System.getProperty("java.class.path");

Java クラス・ローダーとその階層
Java クラス・ローダーとその階層

IBM WebSphere Application Server のクラス・ローダーとは?

IBM WebSphere Application Server を起動すると、カスタム・クラス・ローダー (IBM WebSphere 拡張クラス・ローダー) によって IBM WebSphere Application Server クラスがロードされます。カスタム・クラス・ローダーを使用することで、クラス・ローダーの振る舞い (例えば、委譲モデル) をカスタマイズすることができます。前述したように、デフォルトでは親のクラス・ローダーがクラスをロードしますが、カスタム・クラス・ローダーでは、親のクラス・ローダーにクラスのロードを委譲する前に、まずは現行のクラス・ローダーでクラスをロードするように、委譲モデルを変更することができます。

EJB モジュール、ユーティリティー jar ファイル、リソース・アダプター・アーカイブ、共有ライブラリーなどの Java EE アプリケーションに関連付けられたクラスは、デフォルトで、WebSphere アプリケーション・クラス・ローダーによってロードされます。この場合も構成を変更して、WebSphere クラス・ローダーを複数のアプリケーションで共有することができます。WebSphere アプリケーション・クラス・ローダーの親は、Java 仮想マシンのアプリケーション・クラス・ローダーです。

Java EE アプリケーションの各 Web モジュールは WAR クラス・ローダーによってロードされますが、アプリケーションに含まれるすべての Web モジュールで 1 つの WAR クラス・ローダーを共有するように、WAR クラス・ローダーのポリシーを構成することができます。WAR クラス・ローダーの親は、WebSphere アプリケーション・クラス・ローダーです。

Java および IBM WebSphere Application Server のクラス・ローダーとその階層
Java および IBM WebSphere Application Server のクラス・ローダーとその階層

実行時のクラスとコンパイル時のクラスとの違い

複数のクラス・ローダーがある場合にとりわけ注意しなければならないことの 1 つは、バイナリー名が同じ複数のクラスまたはインターフェースを、それぞれに異なるクラス・ローダーでロードする可能性があることです。

バイナリー名が同じ複数のクラスまたはインターフェースが存在するということは、コンパイル時に、それらのクラスまたはインターフェースが存在するということです。

同じバイナリー名を持つクラスまたはインターフェースが存在し、コンパイル時にも同じクラスまたはインターフェースが存在するとしても、同じクラス・ローダーによってロードされなければ、実行時には異なるクラスになります。

複数のクラスが同じクラス・ローダー上で同じバイナリー名でロードされた場合、それらのクラスは実行時にも同じクラスとなります。

テスト・ケース 1

これから、クラスおよびクラス・ローダーのリークを生成するために、LeakClassLoader という名前のカスタム・クラス・ローダーを作成します。クラス・ローダーを作成するには java.lang.ClassLoader を継承するという方法もありますが、ここでは時間を節約するために、loadClass メソッドをオーバーライドして java.net.URLClassLoader を継承します。

java.lang.ClassLoader の loadClass メソッドは、クラスがすでにロードされているかどうかをチェックします。クラスがまだロードされていなければ、loadClass メソッドは親のクラス・ローダーにクラスの検索を委譲します。親のクラス・ローダーでそのクラスを見つけられなかった場合、loadClass メソッドは現行のクラス・ローダーでクラスをロードするために findClass メソッドを呼び出します。

LeakClass でリークを発生させるために、LeakClassLoader に含まれる java.net.URLClassLoader の loadClass メソッドをオーバーライドします。これにより、親のクラス・ローダーが LeakClass を検索することがなくなり、findClass メソッドが呼び出されて、現行のクラス・ローダーが LeakClass をロードすることになります。これ以外のクラスに対しては、loadClass メソッドは java.lang.ClassLoader の loadClass メソッドを呼び出します。

リスト 1. LeakClassLoader.java
/*
 * Author : Jinwoo Hwang, jinwoo@us.ibm.com
 * LeakClassLoader.java
 * (C) Copyright IBM Corp. 2013
 */
package com.ibm.jinwoo.classloader.testcase;

import java.net.URL;
import java.net.URLClassLoader;

public class LeakClassLoader extends URLClassLoader {
	public LeakClassLoader() {
		super(new URL[] {
getContextClassLoaderClassPath()
});
	}
	
	@Override
	public Class<?> loadClass(String className) throws ClassNotFoundException {
if (className != null && className.equals("com.ibm.jinwoo.classloader.testcase.LeakClass")
                        ) {
			return findClass(className);
		} else {
			return super.loadClass(className);
		}
	}

	static URL getContextClassLoaderClassPath() {
		return Thread.currentThread().getContextClassLoader().getResource(".");
	}
}

LeakTest.java では、InstanceGenerator の newInstance メソッドで新しいインスタンスを作成し、これらの新規インスタンスを ArrayList に追加します。

リスト 2. LeakTest.java
/*
 * Author : Jinwoo Hwang, jinwoo@us.ibm.com
 * LeakTest.java
 * (C) Copyright IBM Corp. 2013
 */
package com.ibm.jinwoo.classloader.testcase;

import java.util.ArrayList;

public class LeakTest {

public static void main(String[] args) 
throws IllegalAccessException,InstantiationException, ClassNotFoundException {
		ArrayList list = new ArrayList();
		while(true){
LeakClassInterface instance = InstanceGenerator.newInstance();

			list.add(instance);
		}
	}
}

InstanceGenerator.java の newInstance() メソッド内で LeakClassLoader の新規インスタンスを作成し、LeakClass クラスをロードして LeakClass のインスタンスを作成し、そのインスタンスを返します。

リスト 3. InstanceGenerator.java
/*
 * Author : Jinwoo Hwang, jinwoo@us.ibm.com
 * InstanceGenerator.java
 * (C) Copyright IBM Corp.
 */

package com.ibm.jinwoo.classloader.testcase;

public class InstanceGenerator {
	public static LeakClassInterface newInstance() throws IllegalAccessException,
			InstantiationException, ClassNotFoundException {
		
		LeakClassLoader classLoader = new LeakClassLoader();
		return (LeakClassInterface) 
		classLoader.loadClass("com.ibm.jinwoo.classloader.testcase.LeakClass")
.newInstance();
	}
}

LeakClass は LeakClassInterface インターフェースを実装します。

リスト 4. LeakClass.java
/*
 * Author : Jinwoo Hwang, jinwoo@us.ibm.com
 * LeakClass.java
 * (C) Copyright IBM Corp. 2013
 */

package com.ibm.jinwoo.classloader.testcase;

public class LeakClass implements LeakClassInterface {

}

LeakClassInterface にはフィールドもメソッドもありません。

リスト 5. LeakClassInterface.java
/*
 * Author : Jinwoo Hwang, jinwoo@us.ibm.com
 * LeakClassInterface.java
 * (C) Copyright IBM Corp. 2013
 */

package com.ibm.jinwoo.classloader.testcase;

public interface LeakClassInterface {

}

これで、クラス/クラス・ローダーのリークを生成するためのすべてのクラスとインターフェースが作成できたので、クラスをコンパイルして LeakTest を実行してみましょう。

java com.ibm.jinwoo.classloader.testcase.LeakTest

クラス・ダンプ

IBM Java ランタイム環境では、IBM Java スレッド・ダンプ (javacore) の一部であるクラス/クラス・ローダーのダンプを取ることができます。UNIX の場合、Java プロセスにシグナル 3 (kill -3、SIGQUIT) を送信すると、ダンプがトリガーされます。Windows システムの場合は、Java コンソールで Ctrl+Break を押すことで、ダンプをトリガーすることができます。

以下に、IBM Javacore から抜粋したクラス・ダンプの例を記載します。これはテキスト・ファイルなので、クラス・ダンプの内容を簡単に理解することができますが、場合によっては何千行ものクラスとクラス・ローダーのエントリーに目を通さなければならず、圧倒されてしまうこともあります。

リスト 6. IBM Javacore から抜粋したクラス・ダンプ
NULL           ------------------------------------------------------------------------
0SECTION       CLASSES subcomponent dump routine
NULL           =================================
1CLTEXTCLLOS  	Classloader summaries
1CLTEXTCLLSS	12345678: 1=primordial,2=extension,3=shareable,
4=middleware,5=system,6=trusted,7=application,8=delegating
2CLTEXTCLLOADER	p---st-- Loader *System*(0x000007FF7E855250)
3CLNMBRLOADEDLIB	Number of loaded libraries 3
3CLNMBRLOADEDCL	Number of loaded classes 438
2CLTEXTCLLOADER	-x--st-- Loader sun/misc/Launcher$ExtClassLoader
(0x000007FF7E859C10),Parent *none*(0x0000000000000000)
3CLNMBRLOADEDLIB	Number of loaded libraries 0
3CLNMBRLOADEDCL	Number of loaded classes 0
2CLTEXTCLLOADER	-----ta- Loader sun/misc/Launcher$AppClassLoader
(0x000007FF7E86FE50), Parent sun/misc/Launcher$ExtClassLoader(0x000007FF7E859C10)
3CLNMBRLOADEDLIB		Number of loaded libraries 0
3CLNMBRLOADEDCL		Number of loaded classes 4
…

幸い、このようなときのためのツールとして IBM ClassLoader Analyzer が用意されているので、早速このツールを使用してクラス・ダンプを分析してみましょう。

IBM ClassLoader Analyzer の起動方法

IBM ClassLoader Analyzer を実行するには、Java ランタイム環境のバージョン 6 以上を使用する必要があります。

お使いのシステムの Java ランタイム環境の構成によっては、オペレーティング・システムのグラフィカル・ユーザー・インターフェースからファイルをクリックするだけで jar ファイルを実行できることもあれば、通常の実行可能ファイルと同じく、コマンドライン・インターフェースから jar ファイルのパス名を入力してファイルを実行しなければならない場合もあります。

いずれの方法でも上手く行きそうにない場合、あるいはオペレーティング・システムを構成する時間がない場合には、コマンドライン・インターフェースを使用する必要があります。

使用方法: <Java path>java –jar ica< version>.jar

例えば、「/usr/java/bin/java -jar ica101.jar」と入力すると、/usr/java/bin/ ディレクトリーの Java ランタイム・ランチャーで、IBM ClassLoader Analyzer のバージョン 1.0.1 が起動されます。

IBM ClassLoader Analyzer の使用方法

このツールを初めて起動する際には、ツールの使用条件を読んで、その諸条件に同意する (または同意しない) ことになります。

ご使用条件
ご使用条件

使用条件に同意しないことを選択した場合、プログラムが終了するので、払い戻しのために速やかに、使用することのないメディアとマニュアルをその購入元に返却する必要があります。プログラムをダウンロードした場合は、プログラムのすべてのコピーを破棄してください。

使用条件に同意すると、以下に示されている、ツールの初期画面が表示されます。

ツールをナビゲートするには、メニュー、ツールバーに表示されたアイコン、またはショートカット・キー (例えば、Ctrl + x というキーの組み合わせを押すとツールが終了します) を使用します。コンソール・フレームには、ツールからの重要なメッセージが表示されます。

以下に、使用条件に同意した場合に表示される初期画面を示します。

初期画面

次に必要な操作は、「File (ファイル)」から javacore を開くことです。

ファイル・メニューの使用方法

「File (ファイル)」メニューには、「Open verbose:class/javacore (verbose:class/javacore を開く)」、「Close Files (ファイルを閉じる)」、「Close All Files (すべてのファイルを閉じる)」、および「Exit (終了)」がメニュー項目としてあります。

ファイル・メニュー

verbose:class/javacore を開く

「File (ファイル)」 -> 「Open verbose:class/javacore (verbose:class/javacore を開く)」の順にメニュー項目を選択してファイルを開くと、Java 詳細クラス・トレースまたは IBM javacore ファイルを分析できるようになります。

verbose:class/javacore を開く
Open verbose:class/javacore (verbose:class/javacore を開く

javacore を選択して、「Open (開く)」ボタンをクリックします。「Open (開く)」メニュー項目の下には、後で使用する「Close Files (ファイルを閉じる)」、「Close All Files (すべてのファイルを閉じる)」、および「Exit (終了)」がメニュー項目としてあります。

ファイルを閉じる

「File (ファイル)」 -> 「Close Files (ファイルを閉じる)」の順に選択すると、選択したファイルが閉じられます。

すべてのファイルを閉じる

「File (ファイル)」 -> 「Close All Files (すべてのファイルを閉じる)」の順に選択すると、すべてのファイルが閉じられます。

終了

「File (ファイル)」 -> 「Exit (終了)」の順に選択すると、ツールが終了します。それでは、先ほどロードした javacore に戻りましょう。

ファイル・リスト

javacore ファイルが正常にロードされると、「File List (ファイル・リスト)」ビューには、そのファイルと分析結果が表示されます。

ファイル・リスト

プロパティー/値の画面の一番下に、以下の分析結果が表示されています。

ClassLoader Leak Suspect
com/ibm/jinwoo/classloader/testcase/LeakClassLoader is loaded 2,185 times (99.82 %)

ツールによって、リークの疑いがあるクラス/クラス・ローダーが明らかにされました。com/ibm/jinwoo/classloader/testcase/LeakClassLoader クラスがロードされた回数は 2,185 回で、クラスのロードの総数の 99.82% を占めています。

リークの確信が持てない場合は、詳細な情報を表示するために、「File List (ファイル・リスト)」内にあるダンプを選択して「Analysis (分析)」メニューをクリックします。

分析メニューの使用方法

「Analysis (分析)」メニューには、「Suspect View (サスペクト・ビュー)」、「Tree View (ツリー・ビュー)」、「ClassLoader View (クラス・ローダー・ビュー)」、および「Library View (ライブラリー・ビュー)」がメニュー項目としてあります。

サスペクト・ビュー

「Analysis (分析)」 -> 「Suspect View (サスペクト・ビュー)」の順に選択すると、リークの疑いに関する詳細を確認することができます。

サスペクト・ビュー・メニュー

「Suspect View (サスペクト・ビュー)」には、com/ibm/jinwoo/classloader/testcase/LeakClassLoader がロードしたクラス数は 2,185 として示されていますが (「Classes (クラス)」列を参照)、このクラス・ローダーでロードされているのは com/ibm/jinwoo/classloader/testcase/LeakClass クラスのみです。com/ibm/jinwoo/classloader/testcase/LeakClassLoader がロードした回数は 2,185 回で (「Count (カウント)」列を参照)、これはロードされた全クラスの 99.8% を占めます。

LeakClassLoader のサスペクト・ビュー

この単純なテスト・ケースでは、4 つのクラス・ローダーしか使用していません。その 1 つは、前に作成したcom/ibm/jinwoo/classloader/testcase/LeakClassLoader です。他の 3 つのクラス・ローダーは何かと言うと、概要で説明した Java クラス・ローダーです。上記の表には、それらの Java クラス・ローダーに該当するブートストラップ・クラス・ローダー (*System*)、拡張クラス・ローダー (sun/misc/Launcher$ExtClassLoader)、アプリケーション・クラス・ローダー (sun/misc/Launcher$AppClassLoader) が示されています。完全なクラス・ローダー名を確認するには、「ClassLoader (クラス・ローダー)」列の幅を調整する必要があります。

多数のクラス・ローダーがある場合には、画面の一番下の「Search Class (クラスの検索)」で正規表現を使用してクラス・ローダーをフィルタリングすることができます。

比較するために、2 番目のクラス・ローダー sun/misc/Launcher$AppClassLoader を選択してください。sun/misc/Launcher$AppClassLoader は、Java 仮想マシンのアプリケーション・クラス・ローダーです。sun/misc/Launcher$AppClassLoader がロードしたクラスは 4 つですが、sun/misc/Launcher$AppClassLoader のインスタンス (カウント) は 1 つだけです。これは、合計クラス数の 0.046% に相当します。

画面の右側には、合計クラス数、クラス・ローダーごとのクラス数、そしてクラス・ローダーがロードしたクラスのリストが表示されます。

sun/misc/Launcher$AppClassLoader の分析
プロパティー
Total Classes (合計クラス数)4
Classes (クラス数)クラス・ローダー当たり 4 クラス: com/ibm/jinwoo/classloader/testcase/InstanceGenerator
com/ibm/jinwoo/classloader/testcase/LeakClassInterface
com/ibm/jinwoo/classloader/testcase/LeakClassLoader
com/ibm/jinwoo/classloader/testcase/LeakTest
AppClassLoader のサスペクト・ビュー

ツリー・ビュー

次に、「Analysis (分析)」メニューに戻って「Tree View (ツリー・ビュー)」を選択し、クラスとクラス・ローダーの階層を視覚化します。

ツリー・ビュー・メニュー

どのクラス・ローダーからどのクラスがロードされたのかは、「Tree View (ツリー・ビュー)」で簡単に確認することができます。

ツリー・ビューに表示されたクラス・ローダー

com/ibm/jinwoo/classloader/testcase/LeakClassLoader は、sun/misc/Launcher$AppClassLoader からロードされていることがわかります。

ツリー・ビューに表示された、クラス・ローダーからロードされたクラス
Tree View, classes loaded from class loaders

com/ibm/jinwoo/classloader/testcase/LeakClassLoader を展開してみましょう。

LeakClass をロードしたのは LeakClassLoader で、それをロードしたのは AppClassLoader で、さらにそれをロードしたのは ExtClassLoader であることがわかります。最も左の列には、その行に示されているクラス・ローダーとそのすべての子クラス・ローダーによってロードされたクラスの合計が示されています。角括弧で囲まれた 2 番目の列に示されているのは、その行のクラス・ローダーからロードされたクラスの数です。3 番目の列にはクラス/クラス・ローダーの名前、4 番目の列にはそのクラス/クラス・ローダーのアドレスが示されています。最後の列に示されるのは、クラス・ローダーのタイプです。クラス・ローダーのタイプは、8 文字で表されます。

クラス・ローダーにクラス・タイプがない場合、5 番目の列は空白になります。

クラス・ローダーのタイプの 8 文字がそれぞれの位置で表している内容は以下のとおりです。

1=primordial (基本)
2=extension (拡張)
3=shareable (共有可能)
4=middleware (ミドルウェア)
5=system (システム)
6=trusted (トラステッド)
7=application (アプリケーション)
8=delegating (委譲)

ExtClassLoader の場合、5 番目の列には -x--st-- と表示されます。

12345678 -x--st--

これは、ExtClassLoader は拡張 (2/x) システム (5/s) トラステッド (6/t) クラス・ローダーであることを意味します。

それでは、いずれ遭遇することになるクラス・ローダーのタイプをいくつか見ていきましょう。

クラス・ローダーのタイプ

  • 基本クラス・ローダー: ブートストラップ・クラス・ローダーとも呼ばれます。このタイプのクラス・ローダーがロードするのは、コアとなる Java API のクラスのみです。これらのクラスは、最も信頼できるクラスであり、JVM のブートストラップに使用されます。
  • 拡張クラス・ローダー: このタイプのクラス・ローダーは、拡張ディレクトリーにあるオプションの拡張パッケージに含まれるクラスをロードすることができます。
  • 共有可能クラス・ローダー: このタイプのクラス・ローダーは、共有クラスをロードします。
  • ミドルウェア・クラス・ローダー: このタイプのクラス・ローダーは、ミドルウェア・クラスをロードします。ミドルウェア・オブジェクトに予想される寿命は、単一のトランザクションより長く、これらのオブジェクトは JVM がリセットされても存続します。
  • システム・クラス・ローダー: ブートストラップ・クラス・ローダー、拡張クラス・ローダー、およびその他の Java システム・クラス・ローダーです。
  • アプリケーション・クラス・ローダー: このタイプのクラス・ローダーは、java.class.pat からクラスをロードし、限定されたセキュリティー・コンテキストで実行されます。

LeakClassLoader を選択すると、右側のプロパティーと値の表に、このクラス・ローダーの詳細が表示されます。

LeakClassLoader のツリー・ビュー

クラスが選択されると、右側のウィンドウに、そのクラスの名前とアドレスが表示されます。

LeakClassLoader のツリー・ビュー

クラス・ローダー・ビュー

「Analysis (分析)」 -> 「ClassLoader View (クラス・ローダー・ビュー)」の順に選択して、「ClassLoader View (クラス・ローダー・ビュー)」を表示してください。

クラス・ローダー・ビュー・メニュー

「ClassLoader View (クラス・ローダー・ビュー)」からは、クラス・ローダー、ロードされたクラス、そしてロードされたクラスの数がわかります。

AppClassLoader は 2,190 のクラスをロードしました。
LeakClassLoader は 2,185 のクラスをロードしました。

クラス・ローダー・ビュー

AppClassLoader を選択すると、AppClassLoader からロードされたクラスを調べることができます。AppClassLoader からは 4 つの異なるクラスがロードされています。AppClassLoader からロードされたクラスの合計数は 2,190 です。

AppClassLoader のクラス・ローダー・ビュー
sun/misc/Launcher$AppClassLoader の分析
プロパティー
Loaded Classes (ロードされたクラス)2,190
Classes (クラス数)4
com/ibm/jinwoo/classloader/testcase/InstanceGenerator
com/ibm/jinwoo/classloader/testcase/LeakClassInterface
com/ibm/jinwoo/classloader/testcase/LeakClassLoader
com/ibm/jinwoo/classloader/testcase/LeakTest

ライブラリー・ビュー

「Analysis (分析)」 -> 「Library View (ライブラリー・ビュー)」の順に選択すると、クラス・ローダーがロードしたライブラリーが表示されます。

ライブラリー・ビュー・メニュー

*System* クラス・ローダーによってロードされたライブラリーは 3 つあります。

ライブラリー・ビュー

Javacore がなくても問題ありません

IBM 以外の Java ランタイム環境の場合

IBM Java ランタイムがないとしたら、さまざまな Java ランタイム環境の実装から広く取得できる Java ヒープ・ダンプを利用するという方法があります。ほとんどの IBM Java プラットフォームでは、メモリーが不足してくるとデフォルトで Java ヒープ・ダンプが自動的に生成されます。ただし、一部の Java プラットフォームでは、コマンド・ラインのオプションを指定する必要があります。

例えば Oracle の JVM では、OutOfMemoryError がスローされた場合に Java 仮想マシンで Java ヒープ・ダンプを生成するには、コマンド・ラインのオプション -XX:+HeapDumpOnOutOfMemoryError を指定する必要があります。このオプションは、当初 Java 1.4.2 アップデート 12 で導入され、Oracle の Java プラットフォームでは Java 5.0 アップデート 7 で導入されました。したがって、古い Java 仮想マシンを使用しているのでない限り、このオプションを指定することができるはずです。

私と同じく OutOfMemoryError がスローされるまでじっと待っている忍耐力がないとしたら、Java ヒープ・ダンプを強制的に生成するという手段があります。それには、IBM Java プラットフォームの場合は環境変数 IBM_HEAPDUMP または IBM_HEAP_DUMP を TRUEに設定して、シグナル 3 (kill -3、SIGQUIT) を Java プロセスに送信します。Oracle の Java 仮想マシンの場合は、コマンド・ラインのオプションを追加する必要があります (例えば、-agentlib:hprof=heap=dump)。

Java ヒープ・ダンプを分析するには、どの Java ヒープ分析ツールを使用しても構いません。ツールがないとしたら、IBM HeapAnalyzer のコピーを無料でダウンロードすることができます (https://www.ibm.com/developerworks/mydeveloperworks/groups/service/html/communityview?lang=ja&communityUuid=4544bafe-c7a2-455f-9d43-eb866ea60091)。IBM HeapAnalyzer は、10 年以上にわたり、世界中の数多くの IBM エンジニアおよびアーキテクトに支持されている Java ヒープ分析ツールです。

このチュートリアルでは、IBM HeapAnalyzer を利用して、クラス・ローダーのリークを検出します。

まず、Java ランチャーで jar ファイルを指定して IBM HeapAnalyzer を起動します。例えば、<java_path>java -Xmx2g -jar ha443.jar とすると、2 GB の Java ヒープが設定された IBM HeapAnalyzer バージョン 4.4.3 が起動します。

IBM HeapAnalyzer

上記の IBM HeapAnalyzer 画面が表示された後は、Java ヒープ・ダンプ・ファイルをツールにドラッグ・アンド・ドロップするか、「File (ファイル)」 -> 「Open (開く)」の順にメニューを選択して、直ちに Java ヒープ・ダンプを開くことができます。あるいは、ツールバーのフッター・アイコンをクリックして Java ヒープ・ダンプをツールに取り込むこともできます。

Java ヒープ・ダンプを開く

「Analysis (分析)」タブをクリックすると、IBM HeapAnalyzer は Java ヒープ・ダンプを構文解析して、リークの疑いのあるオブジェクトを表示します。IBM HeapAnalyzer の分析によると、LeakClass は Java ヒープの約 98.2% を使用していることがわかります。

IBM HeapAnalyzer による分析結果

「Summary (要約)」タブには、Java ヒープ・ダンプの詳細情報が表示されます。

IBM HeapAnalyzer による Java ヒープ・ダンプ情報

「Leak Suspect (リーク・サスペクト)」タブをクリックすると、リークの疑いのあるオブジェクトだけに絞られた表を表示することができます。リークの疑いのあるオブジェクトのそれぞれを調べるために、マウスを右クリックしてコンテキスト・メニューを呼び出して「List all entries (すべてのエントリーを表示)」をクリックしてください。

IBM HeapAnalyzer の「List all entries (すべてのエントリーを表示)」メニュー

これにより、LeakClass のすべてのエントリーが、TotalSize 順にソートされて表示されます。

IBM HeapAnalyzer で検出された、リークの疑いのあるオブジェクト

各 LeakClass が参照ツリー・ビューでどのように表示されるのかを調べるには、マウスを右クリックして「Find object in tree view (ツリー・ビュー内でオブジェクトを検索)」コンテキスト・メニューを呼び出してください。

IBM HeapAnalyzer の「ツリー・ビュー内でオブジェクトを検索」メニュー

これにより、参照ツリー・ビュー内に LeakClass のクラス・ローダーとそれに対応する LeakClass オブジェクトが表示されます。

IBM HeapAnalyzer のツリー・ビュー

Java ヒープ・ダンプがないとしても問題ありません

詳細クラス・トレースの導入

Java ヒープ・ダンプも使用できないとしたら、Java ランチャー・オプションの -verbose:class を有効にするという手段があります。ただし、その場合にはおそらく Java 仮想マシンを再起動する必要があります。

Java ランチャーには、ロードされた各 Java クラスに関する情報を表示する -verbose:class オプションが用意されています。

リスト 7. -verbose:class トレース
class load: java/lang/Object
class load: java/lang/J9VMInternals
class load: java/io/Serializable
class load: java/lang/reflect/GenericDeclaration
class load: java/lang/reflect/Type
class load: java/lang/reflect/AnnotatedElement
class load: java/lang/Class
class load: java/lang/Cloneable
class load: java/lang/Comparable
class load: java/lang/CharSequence
class load: java/lang/String
class load: java/util/Comparator
class load: java/lang/String$CaseInsensitiveComparator
class load: java/io/ObjectStreamField
class load: java/lang/Class$CacheKey
class load: java/lang/ref/ReferenceQueue
class load: java/lang/ref/Reference
class load: java/lang/ref/PhantomReference
class load: java/lang/ClassLoader
class load: java/security/cert/Certificate
class load: java/lang/ref/SoftReference
…

このトレースを有効にすると、Java コンソールにトレース・エントリーが表示されますが、Java コンソールはトレースを保管する場所としては、おそらく最適でありません。

Unix システムでは、リダイレクトするだけでクラス・トレースの出力先をファイルに変更することができます。

java -verbose:class com.ibm.jinwoo.classloader.testcase.LeakTest > verboseclass.log

Windows システムでは、リダイレクトとハンドルの複製によって、クラス・トレースの出力先をファイルに変更することができます。

java -verbose:class com.ibm.jinwoo.classloader.testcase.LeakTest > verboseclass.log 2>&1

verbose:class を開く

「File (ファイル)」 -> 「Open verbose:class/javacore (verbose:class/javacore を開く)」の順にメニュー項目を選択してファイルを開くと、Java 詳細クラス・トレースを分析できるようになります。

詳細クラス・トレースがロードされるとすぐに、疑いのあるクラスがその下に示されます。

詳細クラス・トレース

疑いのあるクラスをクリックすると、そのクラスのロード元コンテナーのリストが表示されます。

疑いのあるクラス

「Analysis (分析)」 -> 「Suspect View (サスペクト・ビュー)」の順に選択して、疑いのあるクラスに関する詳細情報を表示してください。このツールは、このクラスが 9,883 回ロードされたことを示しており、クラスをロードしたクラス・ローダーのリストも表示しています。

サスペクト・ビュー

「Analysis (分析)」 -> 「Tree View (ツリー・ビュー)」の順に選択すると、クラス/クラス・ローダーのツリー表現が表示されます。この表示から、コンテナー・クラスローダーが 9,887 のクラスをロードしたことがわかります。

ツリー・ビュー

テスト・ケース 2

皆さんの環境で以前に java.lang.ClassCastException が発生したときのことを憶えていますか?詳細クラス・トレースと IBM ClassLoader Analyzer を使用すれば、その問題の根本原因を突き止めることができます。ここでは、あるランタイム・クラスからインスタンス化されたオブジェクトを別のランタイム・クラスのオブジェクトでキャストすることによって、ClassCastException を発生させます。具体的には、cls.newInstance() から返されるインスタンスは、カスタム・クラス・ローダーによってロードされたクラスからインスタンス化されており、そのインスタンスを別のクラス・ローダーによってロードされたクラスでキャストします。この 2 つのクラスのバイナリー名は同じですが、これらのクラスをロードするクラス・ローダーが異なることから、異なるランタイム・クラスになります。

リスト 8. TestClassCastException.java
/*
 * Author : Jinwoo Hwang, jinwoo@us.ibm.com
 * TestClassCastException.java
 * (C) Copyright IBM Corp.
 */
package com.ibm.jinwoo.classloader.classcastexception;

import java.net.URL;
import java.net.URLClassLoader;

public class TestClassCastException {

	public static void main(String[] args) 
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
		ClassLoader classLoader = new URLClassLoader
(new URL[]{getContextClassLoaderClassPath()},null);
		Class cls = classLoader.loadClass
("com.ibm.jinwoo.classloader.classcastexception.TestClassCastException");
TestClassCastException instance = 
		(TestClassCastException)cls.newInstance();
	}
	static URL getContextClassLoaderClassPath() {
		return Thread.currentThread().getContextClassLoader().getResource(".");
	}
}

TestClassCastException.java をコンパイルして実行してください。

リスト 9. ClassCastException
Exception in thread "main" java.lang.ClassCastException: 
com.ibm.jinwoo.classloader.classcastexception.TestClassCastException incompatible with 
com.ibm.jinwoo.classloader.classcastexception.TestClassCastException
	at com.ibm.jinwoo.classloader.classcastexception.TestClassCastException.main
   (TestClassCastException.java:15)

上記から、java.lang.ClassCastException が発生したことがわかります。何が問題だったのかを突き止めるには、Java ランチャーに用意されている -verbose:class コマンド・ライン・オプションを指定して、Java クラス・トレースを有効にする必要があります。

Unix システムでは、リダイレクトするだけでクラス・トレースの出力先をファイルに変更することができます。

java -verbose:class com.ibm.jinwoo.classloader.classcastexception.TestClassCastException > classcast.log

Windows システムでは、リダイレクトとハンドルの複製によって、クラス・トレースの出力先をファイルに変更することができます。

java -verbose:class com.ibm.jinwoo.classloader.classcastexception.TestClassCastException > classcast.log 2>&1

「File (ファイル)」 -> 「Open verbose:class/javacore (verbose:class/javacore を開く)」の順にメニュー項目を選択するか、詳細クラス・トレースをツールにドラッグ・アンド・ドロップしてください。詳細クラス・トレースがロードされると同時に、ツールにより、疑いのあるクラスが明らかにされます。

詳細クラス・トレース

疑いのあるクラスをクリックすると、その下にクラス・コンテナーが一覧表示されます。このリストから、クラスが 2 つの異なるコンテナーからロードされていることがわかります。コンテナーとは、クラスのロード元です。コンテナーは、jar ファイルなどのファイルである場合や、ディレクトリーである場合、あるいはクラス・ローダーによるクラスのロード元となり得る、これら以外の任意のリソースである場合があります。

この例では、TestClassCastException クラスがディレクトリーからロードされていることが示されています。

クラス・コンテナー

「Analysis (分析)」 -> 「Suspect View (サスペクト・ビュー)」の順に選択することで、疑いのあるすべてのオブジェクトを一覧表示することができます。この例では、複数のコンテナーが表示されているクラスが興味の対象となります。そのようなクラスは、複数の異なるクラス・ローダーから複数回ロードされている可能性が高いためです。幸い、この例で複数のコンテナーが表示されているクラスは 1 つしかありませんが、完璧とは言えない環境では、該当するクラスがこれよりも多く表示されることが見込まれます。

サスペクト・ビュー

TestClassCastException クラスを選択して、このクラスに関する詳細情報を表示してみましょう。すると、右側のプロパティー/値の表に、コンテナーのリスト、ロードされた回数、およびクラスの名前が表示されます。

コンテナーのリスト

いずれかのコンテナーをクリックすると、コンテナーが展開されて、そのコンテナーからロードされたクラスを確認することができます。

詳細クラス・トレース: コンテナー・ビュー

今度は、「Analysis (分析)」 -> 「Tree View (ツリー・ビュー)」の順にメニュー項目を選択して、ツリー・ビューを調べてみましょう。

ツリー・ビュー・メニュー項目

ツリー・ビューを見てください。ツリー・ビューでは、コンテナーと各コンテナーのクラスを確認することができます。この例では、System コンテナーから 427 のクラスがロードされ、ディレクトリーから 2 つのクラスがロードされていることがわかります。角括弧で囲まれた数値が、そのコンテナーからクラスがロードされた回数です。

詳細クラス・トレースのツリー・ビュー

ツリー・ビューでコンテナーを選択すると、右側のウィンドウに、コンテナー名、そのコンテナーからロードされたクラスの数、ロードされたクラスのリストといった詳細情報が表示されます。同じバイナリー名を持つクラスが数回ロードされている場合、「Loaded Classes (ロードされたクラス)」に、重複するエントリーが表示されます。

詳細クラス・トレースのツリー・ビューの詳細

ここまでの作業で、同じ 1 つの問題を異なる観点で診断することができました。最後に、IBM ClassLoader Analyzer のその他の各種機能についても簡単に見てみましょう。

オプション・メニューの使用方法

「Options (オプション)」メニューには、「User Interface Profiles (ユーザー・インターフェース・プロファイル)」、「Help Contents Browser (ヘルプ・コンテンツ・ブラウザー)」、「Show Console (コンソールの表示)」、および「Clear Console (コンソールのクリア)」がメニュー項目としてあります。

オプション・メニュー

ユーザー・インターフェース・プロファイル

ユーザー・インターフェースのエクスペリエンスを変更するには、「Options (オプション)」 -> 「User Interface Profiles (ユーザー・インターフェース・プロファイル)」の順にメニュー項目を選択します。ユーザー・インターフェース・プロファイルの選択肢には、「Nimbus」、「System (システム)」、「Java」があります。

ユーザー・インターフェース・プロファイル

ヘルプ・コンテンツ・ブラウザー

ツールのヘルプ・コンテンツ文書を読むには、「Options (オプション)」 -> 「Help Contents Browser (ヘルプ・コンテンツ・ブラウザー)」の順にメニュー項目を選択し、システムの Web ブラウザーまたはツールの内部 Web ブラウザーのどちらかを選択することができます。

ヘルプ・コンテンツ・ブラウザー

ヘルプ・メニューの使用方法

「Help (ヘルプ)」メニューには、「Help Contents (ヘルプ・コンテンツ)」、「About (製品情報)」、および「License Agreement (ご使用条件)」がメニュー項目としてあります。

ヘルプ・メニュー

ヘルプ・コンテンツ

「Help (ヘルプ)」 -> 「Help Contents (ヘルプ目次)」の順に選択すると、このツールのヘルプ文書を読むことができます。

ヘルプ・コンテンツ
ヘルプ・コンテンツ

製品情報

ツールに関する基本情報を読むには、「Help (ヘルプ)」 -> 「About (製品情報)」の順にメニュー項目を選択します。

IBM ClassAnalzyer に関する情報

ご使用条件

「Help (ヘルプ)」 -> 「License Agreement (ご使用条件)」の順にメニュー項目を選択すると、ご使用条件が表示されます。

ご使用条件

まとめ

このチュートリアルでは、クラス・ローダーの基本概念と、Java 仮想マシンおよび IBM WebSphere Application Server ランタイムのさまざまなタイプのクラス・ローダー、クラス・ロード・ポリシー、ロード・メカニズムについて説明しました。実例として、カスタム・クラス・ローダーを作成し、クラス/クラス・ローダーのリークとクラスのキャスト例外を生成しました。また、IBM ClassLoader Analyzer および IBM HeapAnalyzer を使用して、Java ヒープ・ダンプ、Java クラス・トレース、IBM Javacore などのさまざまな成果物の問題を診断しました。

要約すると、IBM ClassLoader Analyzer および IBM HeapAnalyzer を使用して、IBM Javacore、Java ヒープ・ダンプ、または Java 詳細クラス・トレースを分析することで、Java クラス/クラス・ローダーのリークや、その他のクラス・ローダー関連の問題 (クラスのキャスト例外など) を診断することができます。


ダウンロード

内容ファイル名サイズ
Test Files for this tutorialclassloadertestcase.zip10KB

参考文献

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=SOA and web services
ArticleID=954708
ArticleTitle=Java クラス・ローダーのメモリー・リークの診断
publish-date=12052013