Sun Java System Application Server Enterprise Edition 8.2 パフォーマンスチューニングガイド

第 4 章 Java 実行システムのチューニング

この章の内容は次のとおりです。

Java 仮想マシンの設定

J2SE 5.0 では、HotSpot Java 仮想マシン (JVM) の 2 つの実装が提供されています。

デフォルトでは、Application Server の用途に適した JVM 設定が使用されます。

管理コンソールの「設定」 > 「config-name」 > 「JVM 設定」 (「JVM オプション」) で JVM 設定を変更することにより、デフォルト値をオーバーライドできます。

J2SE 5.0 でのサーバークラスマシン検出の詳細については、「サーバークラスマシンの検出」を参照してください。

JVM の詳細については、「JavaTM 仮想マシン」を参照してください。

メモリーとガベージコレクションの管理

アプリケーションの効率は、メモリーとガベージコレクションをどれだけ適切に管理するかによって左右されます。次の各節では、メモリー機能と割り当て機能の最適化について説明しています。

ガベージコレクタのチューニング

ガベージコレクション (GC) では、以前にオブジェクトに割り当てられ、現在は不要になったヒープスペースが再生されます。デッドオブジェクトを見つけて削除するプロセスによって、アプリケーションが停止状態になったり、25% ものスループットが消費されたりすることがあります。

ほとんどすべての Java 実行時環境には、世代別オブジェクトメモリーシステムと高性能の GC アルゴリズムが備わっています。世代別メモリーシステムでは、ヒープが、入念にサイジングされた「世代」と呼ばれる複数のパーティションに区切られます。世代別メモリーシステムの効率は、ほとんどのオブジェクトの生存期間が短いという観測に基づいています。それらのオブジェクトが蓄積されてメモリー不足の状態になると、GC が強制的に実行されます。

ヒープスペースは、古い世代と新しい世代に分けられます。新しい世代は、新しいオブジェクトの領域 (Eden) と、2 つの Survivor 領域で構成されます。JVM では、新しいオブジェクトは Eden 領域に割り当てられ、生存期間が長いオブジェクトは新しい世代から古い世代に移動されます。

若い世代では、Eden と 2 つの下位領域 (Survivor 領域) を用いる高速コピーガベージコレクタが使用され、生存しているオブジェクトが一方の Survivor 領域からもう一方の Survivor 領域にコピーされます。若い世代の領域で複数回のコレクションを経ても生存しているオブジェクトは、永続化されます。つまり寿命の長い (tenured) 世代にコピーされます。寿命の長い (tenured) 世代はサイズが大きいため、すぐにいっぱいになることはありません。そのため、ガベージコレクションはそれほど頻繁に行われませんが、1 回のコレクションにかかる時間は若い世代の領域のみのコレクションよりも長くなります。寿命の長い (tenured) 世代の領域のコレクションは、全世代のコレクションとも呼ばれます。

頻繁に行われる若い世代の領域のガベージコレクションは短時間 (数ミリ秒) ですが、全世代のコレクションはもっと時間がかかります (ヒープサイズに応じて数 10 ミリ秒から数秒)。

CMS (Concurrent Mark Sweep ) など、その他の GC アルゴリズムは増分的で、フル GC はいくつかの増分区分に分けられます。このため、短時間の中断が生じる可能性が高くなります。このプロセスは、オーバーヘッドを伴うため、企業向けの Web アプリケーションでは使用されません。

新しい世代がいっぱいになると、生存しているオブジェクトを古い世代に移動するマイナーコレクションがトリガーされます。古い世代がいっぱいになると、オブジェクトヒープ全体を対象とするメジャーコレクションがトリガーされます。

HotSpot と Solaris JDK はどちらも、スレッドローカルなオブジェクト割り当てプールを使用して、ロックされない高速でスケーラブルなオブジェクト割り当てを行います。そのため、ほとんどの場合、カスタムオブジェクトプールは必要ありません。オブジェクト構築コストが高く、実行プロファイルに大きく影響する場合にのみ、プールを検討します。

ガベージコレクションアルゴリズムの選択

フル GC 中の中断時間が 4 秒を超えると、HADB でのセッションデータの持続に断続的に失敗する可能性があります。

GC が進行している間、Application Server は動作していません。中断が長すぎると、HADB で既存の接続がタイムアウトになります。その後、アプリケーションサーバーが動作を再開したときに、それらの接続を使用してセッションデータを持続しようとすると、HADB によるエラーが生成されます。生成されるエラーは、「セッションデータの保存に失敗しました」、「トランザクションが中止されました」、「HADB サーバーへの接続に失敗しました」などです。

この問題が発生しないようにするには、GC アルゴリズムとして CMS コレクタを使用します。このコレクタはほとんど絶え間なく実行されているので、使用率の高いシステムではスループットが低下する可能性があります。ただし、ガベージコレクタが低頻度で実行される場合に生じる可能性がある長い中断は回避できます。

ProcedureCMS コレクタを使用する

  1. システムの CPU 使用率が 100% ではないことを確認します。

  2. 管理ガイドの説明に従って、HADB タイムアウトを設定します。

  3. サーバーインスタンスで CMS コレクタを設定します。

    これを行うには、次の JVM オプションを追加します。

    • -XX:+UseConcMarkSweepGC

    • -XX:SoftRefLRUPolicyMSPerMB=1

補足情報

HotSpot ガベージコレクションを監視するには、jvmstat ユーティリティーを使用します。「詳細情報」を参照してください。

ガベージコレクタのチューニングの詳細については、『Tuning Garbage Collection with the 5.0 Java Virtual Machine』を参照してください。

ガベージコレクションの追跡

ガベージコレクションのパフォーマンスは、主にスループットポーズによって測定されます。スループットは、GC 以外のアクティビティーに費やされた合計時間の割合です。ポーズは、GC が原因でアプリケーションが応答していないように見える時間です。

そのほかにも、フットプリント即応性の 2 つの考慮事項があります。フットプリントは JVM プロセスの作業サイズで、ページとキャッシュ行で測定されます。即応性は、オブジェクトがデッド状態になってからメモリーが利用可能になるまでの時間です。分散システムでは、これは重要な考慮事項です。

各世代のサイズを決定するときは、これら 4 つのメトリック間のトレードオフを考慮します。たとえば、若い世代を大きくすれば、スループットを最大にできるかもしれませんが、フットプリントと即応性は犠牲にされます。逆に、若い世代を小さくして 増分 GC を使用すれば、ポーズを最小にできるので即応性は増しますが、スループットは低下します。

JVM 診断の出力に、ガベージコレクションによるポーズに関する情報が表示されます。サーバーを冗長モード (asadmin start-domain --verbose domain コマンドを使用) で起動する場合は、コマンド行引数の -verbose:gc により、コレクションのたびに情報が出力されます。この JVM フラグで生成される情報の出力例を次に示します。

[GC 50650K->21808K(76868K), 0.0478645 secs]
 [GC 51197K->22305K(76868K), 0.0478645 secs]
 [GC 52293K->23867K(76868K), 0.0478645 secs]
 [Full GC 52970K->1690K(76868K), 0.54789968 secs]

各行の最初の数字は、GC 前のライブオブジェクトの合計サイズ、2 番目の数字は GC 後のライブオブジェクトのサイズ、括弧内の数字は合計空き容量 (ヒープの合計容量から一方の Survivor 領域を差し引いたもの) を示します。最後の数字は、GC に要した時間です。この例は、3 回のマイナーコレクションと 1 回のメジャーコレクションが行われたことを示しています。最初の GC では、コレクションの前は 50650K バイトのオブジェクトが存在していましたが、コレクションのあとは 21808K バイトになりました。つまり、28842K バイトのオブジェクトがデッド状態で収集されたことになります。ヒープの合計サイズは 76868K バイトです。コレクションプロセスには 0.0478645 秒を要しました。

その他の有用な監視オプションを次に示します。

ガベージコレクタのその他の設定

クラスの動的な生成および読み込みを行わないアプリケーションでは、永続的な世代のサイズは GC のパフォーマンスに影響しません。クラスの動的な生成および読み込みを行うアプリケーション (JSP アプリケーションなど) では、永続的な世代がいっぱいになるとフル GC がトリガーされるので、永続的な世代のサイズが GC のパフォーマンスに影響します。永続的な世代の最大サイズを調整するには、-XX:MaxPermSize オプションを使用します。

System.gc() メソッドを使用してアプリケーションから GC を明示的に呼び出せますが、メジャーコレクションが強制され、大規模なシステムではスケーラビリティーが抑制されるので、この方法はお勧めできません。-XX:+DisableExplicitGC フラグを使用して明示的な GC を無効にすることをお勧めします。

Application Server では、管理モジュールの RMI を使用して監視が行われます。RMI ベースの分散アプリケーションのガベージコレクションでは、時々ローカルコレクションを行う必要があるため、RMI では定期的なフルコレクションが強制されます。これらのコレクションの頻度は、-sun.rmi.dgc.client.gcInterval プロパティーによって制御します。たとえば、- java -Dsun.rmi.dgc.client.gcInterval=3600000 と指定すると、デフォルトの 1 分ごとではなく、1 時間ごとに明示的なコレクションが行われます。

Java 仮想マシンの属性を指定するには、管理コンソールを使用して、「config-name」 > 「JVM 設定」 (「JVM オプション」) でプロパティーを設定します。

Java ヒープのチューニング

ここでは、パフォーマンスを向上させるための Java ヒープのチューニングに関する項目について説明します。

Java ヒープのサイジングのガイドライン

最大ヒープサイズは、プロセスあたりの最大アドレス空間によって決まります。次の表に、各種プラットフォームのプロセスあたりの最大アドレス値を示します。

表 4–1 プロセスあたりの最大アドレス空間

オペレーティングシステム 

プロセスあたりの最大アドレス空間 

Redhat Linux 32 bit 

2G バイト 

Redhat Linux 64 bit 

3G バイト 

Windows 98/2000/NT/Me/XP 

2G バイト 

Solaris x86 (32 bit) 

4G バイト 

Solaris 32 bit 

4G バイト 

Solaris 64 bit 

テラバイト 

プロセスにもスタックやライブラリなどのための領域が必要なため、最大ヒープスペースは常に、プロセスあたりの最大アドレス空間よりも小さくなります。割り当て可能な最大ヒープスペースを決定するには、プロファイリングツールを使用して、メモリーがどのように使用されているかを調べます。プロセスで使用される最大スタック空間と、ライブラリやその他のメモリー構造で使用されるメモリー量を測定します。最大アドレス空間とこれらの値の合計の差が、ヒープに割り当て可能なメモリー量になります。

ヒープサイズを大きくしたり、別のガベージコレクタを使用したりすることで、パフォーマンスを改善できます。一般に、長時間実行されるサーバーアプリケーションでは、複数のプロセッサを持つマシンで J2SE スループットコレクタを使用し ( -XX:+AggressiveHeap)、ヒープサイズはマシンの空きメモリーをできるだけ多く使用した大きさにします。

ヒープのチューニングパラメータ

次の JVM パラメータでヒープサイズを制御できます。

-Xms パラメータと -Xmx パラメータはそれぞれ、最小ヒープサイズと最大ヒープサイズを定義します。世代がいっぱいになると GC が行われるので、スループットは使用可能メモリーの量に反比例します。デフォルトでは、JVM はコレクションのたびにヒープサイズを拡大または縮小して、ライブオブジェクトに対する空き容量の割合を一定の範囲内に維持しようとします。この範囲は、-XX:MinHeapFreeRatio=minimum パラメータと -XX:MaxHeapFreeRatio=maximum パラメータでパーセント値として設定され、合計サイズは -Xms-Xmx によって制限されます。

ヒープサイズを固定する場合は、-Xms-Xmx に同じ値を設定します。ヒープサイズが拡大または縮小されると、JVM では、事前に定義された NewRatio を維持するために、古い世代と新しい世代のサイズが再計算されます。

NewSize パラメータと MaxNewSize パラメータは、新しい世代の最小サイズと最大サイズを制御します。新しい世代のサイズを固定するには、これらのパラメータに同じ値を設定します。若い世代を大きくするほど、マイナーコレクションの発生回数は少なくなります。古い世代に対する若い世代のサイズの比率は、NewRatio で制御されます。たとえば、-XX:NewRatio=3 と設定すると、古い世代と若い世代の比率は 1:3 になり、Eden 領域と Survivor 領域の合計サイズはヒープサイズの 4 分の 1 になります。

デフォルトでは、Application Server は Java HotSpot Server JVM によって起動されます。Server JVM のデフォルトの NewRatio は 2 で、古い世代がヒープの 3 分の 2、新しい世代が 3 分の 1 を占めます。新しい世代を大きくするほど、生存期間の短いオブジェクトを数多く収容できるので、時間のかかるメジャーコレクションの回数が減ります。同時に、古い世代も、生存期間の長いオブジェクトを多数保持するのに十分な大きさがあります。

Java ヒープのサイズを決定するには、次の手順に従います。

最新のデフォルト設定については、「Java HotSpot VM Options」を参照してください。


例 4–1 Solaris でのヒープ設定

Solaris 上の Application Server で大きなアプリケーションに対して使用されるヒープ設定の例を次に示します。

-Xms3584m
 -Xmx3584m
 -verbose:gc
 -Dsun.rmi.dgc.client.gcInterval=3600000

Survivor 領域の比率の設定

SurvivorRatio パラメータは、2 つの Survivor 領域のサイズを制御します。たとえば -XX:SurvivorRatio=6 と設定すると、各 Survivor 領域と Eden 領域の比率は 1:6 になり、それぞれの Survivor 領域のサイズは若い世代全体の 8 分の 1 になります。Solaris のデフォルトは 32 です。Survivor 領域が小さすぎると、コピーコレクションがオーバーフローし、その分が直ちに古い世代に送られてしまいます。Survivor 領域が大きすぎると、無意味になります。GC のたびに、JVM は、寿命の長い (tenured) 世代に送るまでにオブジェクトをコピーできる回数 (tenure しきい値と呼ばれる) を判断します。このしきい値は、Survivor 領域の使用量が半分に維持されるように選択されます。

このしきい値と、新しい世代のオブジェクトのコピー回数を表示するには、-XX:+PrintTenuringDistribution オプションを使用します。これは、アプリケーションの生存期間の分布を観測するのに役立ちます。

Windows での DLL ベースアドレスの再割り当て

JVM は、初期化時に、 -Xms 設定を使用してヒープを割り当てようとします。Application Server DLL のベースアドレスによって、利用可能な連続アドレス空間の量が制限され、JVM の初期化に失敗することがあります。Java メモリーで利用可能な連続アドレス空間の量は、DLL に割り当てられたベースアドレスによって異なります。Application Server DLL のベースアドレスの再割り当てを行うことにより、利用可能な連続アドレス空間の量を増やせます。

読み込みアドレスの衝突を避けるには、Visual Studio および Platform SDK に付属する rebase ユーティリティーを使用して、優先ベースアドレスを設定します。rebase ユーティリティーを使用して Application Server DLL のベースアドレスを割り当て直すことで、読み込み時の再配置を避け、Java ヒープで利用可能なプロセスメモリーを増やします。

衝突の原因となる可能性があるデフォルト以外のベースアドレスを持つ Application Server DLL がいくつかあります。次に例を示します。

利用可能な最大連続アドレス空間を実質的に増やすには、これらのライブラリをシステム DLL (msvcrt.dll0x78000000) の近くに移動します。すべての DLL でベースアドレスの再割り当てを行えるので、Application Server をインストールしたら、DLL のベースアドレスの再割り当てを実行してください。

ProcedureApplication Server DLL のベースアドレスの再割り当てを行う

始める前に

ベースアドレスの再割り当てを実行するには、次のものが必要です。

  1. 次のコマンドを使用して、install_dir\bin をデフォルトディレクトリにします。


    cd install_dir\bin
  2. 次のコマンドを入力します。


    rebase -b 0x6000000 *.dll
  3. dependencywalker ユーティリティーを使用して、DLL のベースアドレスの再割り当てが正しく行われたことを確認します。

    詳細については、Dependency Walker の Web サイトを参照してください。

  4. Java ヒープのサイズを大きくし、管理コンソールの「JVM 設定」ページで、それに応じた JVM オプションを設定します。

  5. Application Server を再起動します。


例 4–2 Windows でのヒープ設定

次に、Windows 上の Sun ONE Application Server で負荷の大きいサーバー中心型のアプリケーションに対して使用されるヒープ設定の例を示します。これは、server.xml ファイルに設定されます。

<jvm-options> -Xms1400m </jvm-options>
<jvm-options> -Xmx1400m </jvm-options>

参照

ベースアドレスの再割り当ての詳細については、MSDN の rebase ユーティリティーに関するページを参照してください。

詳細情報

JVM のチューニングの詳細については、次の Web ページを参照してください。