Java VM の Metaspace (JDK 7 以前の場合は PermGen space) には、ロードされたクラスの情報が格納されます。
通常、アプリケーションを配備解除/再配備すると配備解除/再配備前の古いアプリケーションのクラスはアンロードされます。
これによって、古いアプリケーションのクラス情報で使用していた Metaspace が解放されます。
しかし、古いアプリケーションのクラスやそのインスタンスへの参照、そのクラスをロードしたクラスローダへの参照がどこかに残っている場合、クラスはアンロードされません。
もしこのような参照が残ってしまうと、再配備を繰り返すたびにロードされたクラスが増えていき、Metaspace が枯渇します。
OutOfMemoryError の直接の原因は、このような意図しない参照が残っていて、再配備をしても古いクラス情報を格納しているメモリが解放されなかったためです。
このような意図しない参照がどこに残っているのかは、ヒープダンプから調べることができます。
WebOTX または Java VM の機能で OutOfMemoryError 発生時にヒープダンプが取得されている可能性があります。
WebOTX のドメインの config ディレクトリに .bin または .hprof という拡張子の大きなサイズのファイルがあれば、そのファイルがヒープダンプです。
もし、自動的に採取されていなければ、何回か再配備を繰り返した後のヒープダンプを採取してください。
採取したヒープダンプは Eclipse Memory Analyzer などのツールを用いて解析し、意図しない参照が残っていないか調べることができます。
Eclipse Memory Analyzer の場合、「Duplicated Classes」を確認すると再配備前の古いアプリケーションのクラスローダと再配備後の新しいアプリケーションのクラスローダの両方でロードされているクラスを見つけることができます。
そこから、再配備前の古いアプリケーションのクラスローダに対して「Path to GC Roots」で参照元をたどることで、どこに意図しない参照が残っているかを調べることができます。
参照元をたどる際は、弱参照 (Weak Reference) およびソフト参照 (Soft Reference) を除いてたどってください。
■ ヒープダンプの採取方法
ヒープダンプを採取するには JDK の jconsole を使用します。
1. jconsole を起動
2. 新規接続ダイアログでリモートプロセスを選択し、接続先、ユーザ名、パスワードを入力して接続を押下
接続情報は、otxadmin 実行時と同じです。
接続先は、[ホスト名]:[ポート番号] で指定します。
例) 接続先: localhost:6212, ユーザ名: admin, パスワード: ****
3. MBeans タブを選択
4. com.sun.management - HotSpotDiagnostic の操作を選択
5. 操作 dumpHeap の引数 p0 にヒープダンプの出力先 (拡張子は .hprof) を指定
サーバ側のパスを指定してください。
6. dumpHeap を押下
操作 dumpHeap の引数 p0 に指定したパス (サーバ側) にヒープダンプが出力されます。
また、以下の JVM オプションを設定することで、OutOfMemoryError 再発時にヒープダンプを採取することができます。
-XX:+HeapDumpOnOutOfMemory
ヒープダンプは、WebOTX のドメインの config ディレクトリに出力されます。
再発時にヒープダンプを採取できるように、上記の JVM オプションの指定をご検討ください。
■ クラスローダのリークが発生する可能性がある箇所
参考情報として、再配備時にアプリケーションのクラスローダがリークする可能性がある箇所をいくつか説明します。
・ JDBC ドライバのクラスおよびインスタンスが java.sql.DriverManager に登録されたままになっている
JDBC ドライバはクラスの初期化時に、自身を java.sql.DriverManager に登録します。
そのため、JDBC ドライバの jar ファイルを war に含めている場合 (WEB-INF/lib/*.jar) は、アプリケーション終了時に java.sql.DriverManager クラスの deregisterDriver メソッドを呼んで、JDBC ドライバを登録解除する必要があります。
そうしないと、配備解除してもアプリケーション用のクラスローダでロードされるJDBC ドライバのクラスおよびインスタンスへの参照が残り、アプリケーションのクラスローダが GC 対象になりません。
アプリケーションの修正が難しい場合は、以下の場所に JDBC ドライバの jar を置くことで、メモリリークを回避できます。
<WebOTXインストールディレクトリ>/domains/<ドメイン名>/lib/
このディレクトリに置かれた jar 内のクラスは、ドメイン共通のクラスローダでロードされます。
JDBC ドライバのクラスがアプリケーション用クラスローダでロードされないので、JDBC ドライバのクラスが残っていてもアプリケーション用クラスローダは GC 対象となります。
なお、上記のディレクトリの jar ファイルを置換・追加・削除する場合は、ドメインの再起動が必要です。
・ アプリケーション内でスレッドを起動し、終了していない
アプリケーション内でスレッドを起動すると、そのスレッドのコンテキストクラスローダはアプリケーションのクラスをロードするクラスローダになります。
そのため、配備解除してもそのスレッドが終了しないまま残っていると、アプリケーションのクラスローダが GC 対象になりません。
アプリケーション内でスレッドを起動した場合は、アプリケーション終了時にスレッドを終了させる必要があります。
明示的に java.lang.Thread を使用していなくても、アプリケーション内でタイマー (java.util.Timer) やスレッドプール (java.util.concurrent.ThreadPoolExecutor) を使用している場合も同様に終了させる必要があります。
・ ThreadLocal 変数にアプリケーションのクラスのインスタンスを格納している
java.lang.ThreadLocal を使用すると、スレッドローカルのオブジェクトはスレッド (java.lang.Thread) のフィールドに格納されます。
ThreadLocal 変数にアプリケーションのクラスのインスタンスを格納している場合、配備解除してもアプリケーションのクラスローダが GC 対象にならない可能性があります。
(ThreadLocal 変数自体への参照が残っていなければ、メモリリークにはなりません)
【対象製品】Application Server
【確認済みのバージョン】すべて
【確認済みのエディション】すべて
【確認済みの対象OS】すべて
【確認済みのJavaバージョン】すべて
【コンポーネント】配備
【カテゴリー】トラブルシューティング