Q 質問 J2EEベースのWebアプリケーション・サーバーを使ったシステムで,運用中に一時的に実行性能が低下することがあります。考えられる原因と対策を教えて下さい。

A 回答 フルガベージ・コレクションが起きていることが考えられます。リソースの後処理に注意しましょう。


 J2EE(Java 2 Platform, Enterprise Edition)アプリケーションの実行性能が一時的に低下する原因の一つには,「ガベージ・コレクション」が挙げられます。Java言語はメモリー管理を自動化しており,動的に確保した領域であっても使わなくなればJavaVM(仮想マシン)が自動的に解放します。このメモリーの自動解放処理がガベージ・コレクションです。

 ガベージ・コレクションの実行中は,ほかの処理は一時的に停止します。通常のガベージ・コレクションはCPUの空き時間に実行されるためアプリケーションへの影響はほとんど無いと考えられますが,“重い”ガベージ・コレクションが発生するとCPUの空き時間内で完了せずに,アプリケーションの実行性能に影響を及ぼすことがあります。

2種類あるガベージ・コレクション

図1●通常のGCとフルGC
通常のGC(ガベージ・コレクション)は新しく生成するオブジェクトのための領域だけを対象とするが,「フルGC」はヒープ領域のすべてを対象とする。フルGCは重い処理になることが多く,フルGCが実行されるとアプリケーションの実行性能に悪影響を与える

 ガベージ・コレクションはJavaVMが制御します。JavaVMの実装は製品に依存するためJavaVM製品すべてに当てはまるとは限りませんが,2種類のガベージ・コレクションを備える製品が多いです。

 一つは通常のガベージ・コレクションで,もう一つが「フルガベージ・コレクション」です。JavaVMが管理するヒープ領域には,(1)新しく生成するオブジェクトのための領域と,(2)長い期間メモリー中に存在するオブジェクトのための領域があります。(1)だけを対象とするのが通常のガベージ・コレクションで,(1)と(2)の両方を対象とするのがフルガベージ・コレクションです(図1[拡大表示] )。

 前述した重いガベージ・コレクションとして問題になりやすいのが,フルガベージ・コレクションです。性能の低下を防ぐには,できるだけフルガベージ・コレクションを実行させないようにすべきです。フルガベージ・コレクションの多くはメモリー・リークによって引き起こされます。Javaの仕様では通常のガベージ・コレクションによってメモリー・リークが起きないとなっていますが,実際には通常のガベージ・コレクションでは認識できないメモリー・リークが発生します。

メモリー・リークが性能低下を招く

 つまり,このメモリー・リークが起きないようにする必要があります。ここで言っているメモリー・リークとは,「アプリケーションは使っていないが,解放されていないメモリー」を指しています。Javaの言語レベルでは通常ガベージ・コレクションによってメモリー・リークは発生しません。しかしAPサーバー製品などミドルウエアを使う場合,メモリーのある領域をミドルウエアが確保しているためにJava VMからはガベージ・コレクションの対象として認識されないものの,その領域はアプリケーションとしては使わない状態になることがあります。

図2●見落としやすい例外対策
finally句の中で起きる例外を見落としやすい。(1)で例外が起きると(2)は実行されない。通常finally句では各種リソースの後処理を記述することが多く,後処理が適切でなければメモリー・リークを招く

 例えば,RDBMSとのやり取りに利用する「ResultSet」などのAPサーバーが提供するリソースは,それらを利用する際にAPサーバーが動的にメモリーを確保します。そのリソースの後処理(close処理など)が呼び出されることで,使用済みメモリーがガベージ・コレクションの対象となります。後処理を忘れた場合,アプリケーションは利用していないにもかかわらずAPサーバーがメモリーを使い続ける状態になり,メモリー・リークが発生します。

 メモリー・リークを起こさないためには,APサーバーが提供するリソースの後処理を確実に呼び出さなければなりません。基本は例外処理対策をきちんと行うことです。

 例外が起きても後処理がスキップされないようにするために,通常は「try-catch-finally」構文の中の,finally句の中にリソースの後処理を記述します。finally句はtry句やcatch句で例外が起きた場合でもスキップされずに実行されるため,確実にリソースの後処理ができます。

 ここで注意しなければならないのは,finally句で起きる例外です。finally句で例外が起きると処理がスキップされます(図2[拡大表示])。finally句ではリソースの後処理を記述する場合が多いため,finally句で起きた例外によってメモリー・リークすることが考えられます。finally句で複数の処理を実行させる場合,finally句の中に「try-catch-finally」構文を入れ子にし,確実にリソースの後処理が実行されるようにしましょう。

(本誌)