Comments
Description
Transcript
HP-UX Java パフォーマンス・チューニング Whitepaper
HP-UX Java パフォーマンス・チューニング Whitepaper 目次 1. はじめに ................................................................................................................................... 3 1-1 チューニング・ルール ...................................................................................................... 3 1-2 チューニング手順 ........................................................................................................... 4 1-2-1 評価(Assess)............................................................................................................ 5 1-2-2 測定(Measurement)................................................................................................. 6 1-2-3 分析(Analyze)......................................................................................................... 7 1-2-4 特定(Identify).......................................................................................................... 7 1-2-5 変更(Tune)............................................................................................................... 7 1-3 HP Javaパフォーマンスツール ....................................................................................... 7 2. パフォーマンス問題の原因と対策 .......................................................................................... 8 2-1 HP-UXカーネル・パラメータ ............................................................................................ 8 2-2 HP-UXネットワーク・パラメータ ....................................................................................... 10 2-3 HP-UXおよびJavaのパッチ ............................................................................................... 11 2-4 ガベージ・コレクション .................................................................................................... 11 2-4-1 ガベージ・コレクションの分析 –Xverbosegc ........................................................... 13 2-4-2 Javaヒープ・メモリの構造 ........................................................................................ 15 2-4-3 Javaガベージ・コレクションのメカニズム ............................................................... 17 2-5 リソース競合 ................................................................................................................... 23 2-5-1 スレッド・スタック・トレース kill –3 .......................................................................... 27 2-5-2 スレッド・ロック問題 – HPjmeterの使用 ................................................................... 28 2-5-3 スレッドの優先度 ..................................................................................................... 29 2-6 リソースを大量に消費するメソッド・コール .................................................................. 30 2-7 メモリ・リーク .................................................................................................................. 32 2-8 HPjmeterを使ったメモリ・リテンションの解析 ................................................................. 35 2-9 ベンチマークとHot Spot ................................................................................................ 40 2-10 その他のJVMオプション ................................................................................................ 43 3. まとめ ....................................................................................................................................... 44 4. 参考文献 .................................................................................................................................. 45 (付録A) ツールを用いた簡単なチューニング例 ................................................................. 46 パフォーマンス問題例 ............................................................................................ 46 HPjmeterを使った解析 ............................................................................................. 46 HPjtuneを使った解析 ............................................................................................... 49 チューニング作業 ................................................................................................... 52 チューニング結果 ................................................................................................... 53 (付録B) HP-UX HotSpot JVMのPermanent領域について ................................................... 54 オプションによるサイズの指定 .............................................................................. 54 Permanent Space不足によるエラー ........................................................................ 55 クラスローダによるPermanent領域へのクラスのロード ...................................... 55 Permanent領域とGarbage Collection ..................................................................... 56 Permanent領域のチューニング ............................................................................... 57 目次(続き) (付録C) HP-UX上でのCヒープとJavaヒープ......................................................................... 58 GlancePlusではメモリ消費量がどのように見えるか ............................................. 58 (付録D) Javaヒープサイズの制限について(PA-RISC版 SDK)........................................... 60 HP-UXの32ビットプロセスにおけるLarge heapサポート ....................................... 60 Large heapとJava .................................................................................................... 60 (付録E) Javaスタックトレースによるリソース競合検出 ..................................................... 62 ロックによるスレッドの同期 .................................................................................... 62 スレッド状態 ............................................................................................................ 63 デッドロック ............................................................................................................. 65 (付録F) OutOfMemoryErrorについて.................................................................................... 66 Javaヒープ(Old領域)不足 ..................................................................................... 66 仮想メモリ不足 ....................................................................................................... 67 カーネルパラメータ値が小さすぎる ..................................................................... 67 (付録G) SDK 1.4での新しいガベージコレクション(GC)................................................... 69 パラレルGC ........................................................................................................... 69 コンカレントGC .................................................................................................... 70 (付録H) HP-UX 11i Version 2(11.23)、Java2 1.4パフォーマンス関連情報 .................... 71 Java2プラットフォーム ............................................................................................ 71 パフォーマンスチューニングツール ...................................................................... 71 カーネルコンフィグレーション ................................................................................ 71 Java2 1.4 JVMパフォーマンス関連強化機能 ........................................................ 73 1 はじめに このドキュメントは、Java 関連のパフォーマンス問題に有用なトラブルシューティング情 報を提供し、HP-UX 上の Java をチューニングし、パフォーマンスをより向上させることを 目的としています。 本ドキュメントはチューニング手法の入門として利用されることを目的としており、全ての 詳細な内容を含むものではありません。詳細については「4 参考文献」のセクションに 書かれているいくつかの Java パフォーマンスに関する本や文献が参考になるでしょう。 本書は Hot Spot Runtime Compiler 1.3.1 以降を含む HP-UX Java Developer’s Kit (SDK) 1.3.X を対象として書かれており、プラットフォームとしては PA-RISC,Itanimum の両方を 対象としています。但し、バージョンによる機能の違いもありますので、バージョン毎の 情報に関する詳細については、SDK のバージョンのリリースノートを参照してください。 ここで記述されているほとんどの内容は以下の web サイトにおいて、より詳細に解説さ れています。 (java products for hp-ux) http://www.hp.com/go/java ➟ information library ➟ brochures and articles ➟ performance tuning java on hp-ux 1-1 チューニング・ ルール [1] パフォーマンス・チューニングにはいくつかの基本的な法則があり、これらはどのような システムに対しても適用できる一般的なものです。技術的な内容を論じる前に、まずこ れらの普遍的なルールをいくつか紹介します。 ルール 1: システムのパフォーマンスは、多くの問題がお互いに複雑に依存している。 システムは多くのコンポーネントが協調して動作しています。そしてこれらはお互いに複 雑な関係を結んでいます。そのため、あらかじめパフォーマンスのボトルネックを特定す ることは非常に困難です。 このような依存性は 2 番目ルールを導きだす要因ともなっています。 ルール 2: パフォーマンス・チューニングは常にトレード・オフを伴う。 システム・チューニングには OS 動作の仕組みやパフォーマンスに影響を与える依存関 係の理解が必要です。ある特定の部分を変更することは、他の部分からその恩恵を奪 うことを意味します。例えば、メモリ利用率を向上させるためのチューニングはファイル・ [1] HP-UX Tuning and Performance, Robert F. Sauers and Peter S. Weygant, Prentice Hall 3 システムのパフォーマンスを低下させることになるかも知れません。アプリケーションの 要求や、メモリとファイル・システムの相互作用を理解することにより、最適なトレード・オ フを選ぶことができます。 このようにボトルネックが存在する場合には、リソースと、リソースに対する要求のバラ ンスをうまく取る必要があります。ボトルネックを解消するためには、リソースを増加させ るか、あるいは要求を減らすか、そのどちらかしかありません。そういった意味でもリソ ースの使用状況を観察することは非常に有用です。 リソース使用状況を観察する際に重要なことは次のルールで示されます。 ルール 3: 観察されるシステムに影響を与えることなくパフォーマンスを観察することはできない。 計測をすることはそれ自体リソースを消費します。また使用するツールの種類によって、 その消費量は異なります。例えば、gpm(Glance のグラフィカル・バージョン)は、vmstat や glance と比べてはるかに大きいメモリを必要とします(5MB 以上)。 ツールはリソースを消費するだけでなく、システムそれ自体をも変えてしまうことに注意 してください。ツールを実行するのに十分なメモリを持たないシステムの場合、ツールが メモリを余分に要求することにより、スラッシング(物理メモリとスワップ領域の間で頻繁 にページをやり取りする作業)が発生するかもしれません。こうなると観察されるシステ ムはもう元のシステムではありません。 システムへの影響がどの程度許されるかというのはツールを選ぶ上での重要な用件で す。状況によって様々なツールを使い分けることが求められます。これは以下の最後の ルールへとつながります。 ルール 4: パフォーマンス問題を解決するために必要な全てのデータを提供してくれる万能ツール は存在しない。 どのような状況でも問題を解決してくれる完璧なツールは存在していません。いろいろな 状況に対応できるようになるためには、たくさんのツールに習熟するしかありません。ま た、複数のツールを使用することで情報をクロス・チェックすることができ、ツールが本当 に正しい情報を提供しているか確認することも可能となります。 1-2 チューニング手順 [2] 問題を解決することに大きなプレッシャーがかかっている場合は、十分な測定や分析を することなく安易に対応を取ってしまいがちです。ほとんどの場合、安易な推測は悪い 結果を招きます。 こうした場当たり的な対応を行う代わりに、以下のような一般的なパフォーマンス・チュ ーニング手法に従ってください。この手順を図示したのが図 1 です。 ► [2] 4 評価(Assess) HP-UX Tuning and Performance, Robert F. Sauers and Peter S. Weygant, Prentice Hall ► ► ► ► 測定(Measure) 分析(Analyze) 特定(Identify) 変更(Tune) start assess measure yes no change in workload interpret & analyze end update/ tune no performance meets criteria? yes 図 1 チューニング手法 1-2-1 評価(Assess) 評価はまず多くの質問に答えることから始まります。これらの質問に答えることで何を、 どこまでチューニングするのかといった基本的な事柄を理解する助けになります。以下 の項目はこの段階で学ぶ必要のあるものです。 ► ► ► ► ► ► ► システム構成 アプリケーション・デザイン パフォーマンスの目標 ピーク負荷、またその期間 システムあるいはアプリケーションで行われた変更点 パフォーマンス問題の発生する期間 各種オプション パフォーマンス分析において最初にとる行動はおそらくユーザー・トランザクションが通 過する全てのコンピューターのマシン構成を書き留めることです。必要となる情報は、 ► ► ► ► ► コンピューターの CPU 数と、それら CPU のスピード (CPU 数は Glance/gpm で見ることができます) メイン・メモリの容量 (同じく Glance/gpm で確認可能) ディスク・スペース (使用領域と未使用領域、df コマンドの出力) オペレーティング・システムのチューニング可能なパラメータ(HPjconfig ツ ールで確認可) オペレーティング・システムに適用されるべきパッチ (これも HPjconfig で可) 5 ► ► 使用されている JVM のバージョン (”java –version”コマンドで入手) JVM に指定しているオプション (-Xms <value> や-Xmx <value> など) Java バージョン情報の例として、”java –version”コマンドの出力を以下に示します。 mode) java version "1.3.1.01-release" Java 2 Runtime Environment, Standard Edition (build 1.3.1.01-release-010816-12:37) Java HotSpotTM Server VM (build 1.3.1 1.3.1.01-release-010816-13:34-PA_RISC2.0 PA2.0, mixed これらのバージョン情報は、Java プログラムのパフォーマンス特性を評価する上で参考 になります。いくつかのパフォーマンス問題は、単純に最新のバージョンの Java SDK に アップグレードすることや、最新のパッチをオペレーティング・システムに適用すること、 Java ランタイムに正しいオプションを指定することで解決することもあります。 最新のバージョンの Java SDK にアップグレードし、最新のパッチ・セットを適用すること が推奨されています。次のセクション(HP-UX カーネル・パラメータ)で詳しく解説する HPjconfig ツールは、正しい HP-UX パッチがインストールされているかどうかをチェックす るためも使用することができます。 $ java –classic ClassName 上は Java バーチャル・マシンの誤った使い方を示しています。この例では、サーバー・ サイドの長期稼動型アプリケーションには推奨されていない、古くて最適化が不十分な Java ランタイム・バージョンが使用されています。この場合、デフォルトの Java ランタイム (HotSpot)の使用が推奨されています。 1-2-2 測定(Measurement) パフォーマンス・チューニングにおける次の段階は測定です。ここでは実際のシステム、 アプリケーションのパフォーマンスを測定します。 HP-UX 上の Java パフォーマンス測定では、sar、top などの標準的なツールに加えて以 下のような方法を使用することができます。 ► ► ► ► Glance/gpm パフォーマンス測定ツール JVM オプション –Xverbosegc JVM オプション –Xeprof と HPjmeter ツール スレッド・スタック・トレース kill –3 これらのツールを使って情報を集めるには時間がかかる場合もありますが、測定と分析 は必ずシステムに変更を加える前に行っておく必要があります。 6 1-2-3 分析(Analyze) 測定が終わった後は、収集したたくさんのデータを分析しなければなりません。ツール によってデータの出力形式は異なり、それらの複雑なデータを正確に解釈する必要があ ります。これは簡単な作業ではありません。システムではいろいろな要素がお互いに複 雑に作用していることを思い出してください。 データの分析方法については、「2 パフォーマンス問題の原因と対策」のセクションで具 体的に説明します。 1-2-4 特定(Identify) データの解釈、分析を行うのはシステムにボトルネックが存在しているかどうかを見極 めるためです。何らかのボトルネックが存在することがわかれば、パフォーマンスを向上 させるためのチューニングに取り掛かることができます。しかし場合によっては、そのボ トルネックを緩和した後に、新たなボトルネックが発生してしまうこともあります。 1-2-5 変更(Tune) ボトルネックが特定されると、その後に取るべき道は 2 つあります。リソースを増加させ るか、あるいはそのリソースへの要求を減らすかです。どちらにしても一度に変更する のは必ず 1 箇所に限定してください。2 箇所以上変更してしまうと、何がパフォーマンス に影響を与えたのかわからなくなります。 また行った変更については逐次ノートなどに記録するようにしてください。変更の履歴を 取ることにより混乱を避けることができます。 1-3 HP Java パフォーマンスツール HP では各チューニングステップ(Assess, Measurement, Analyze, Identify, Tune)を サポートする各種ツールを提供しています。次表は各ツール (GlancePlus,HPjmeter,HPjtune,Hpjconfig,OutofBox)が利用されるチューニングス テップ、調査対象、チューニング対象を示したものです。 7 Assess Meas ure ○ GlancePlus ○ HPjmeter HPjtune ○ Analyze and Identify ○ ○ ○ Tune 調査対象 チューニング対象 ○ CPU I/O メモリ ネットワーク プロセス, スレッド, システムコール, 競合 カーネルパラメータ システムパラメータ メモリサイズ Java オプション Java アプリケーション ○ Java ヒープとGC Java スレッド グラフ – CPU, 時間, メソッドコール ロック・コンテンション Objectの生成 Java ヒープサイズ Java オプション Java アプリケーション ボトルネックの特定 Java ヒープとGC動作の 詳細 Java ヒープサイズ Java オプション パッチ, ネットワークとカ ーネルのパラメータ パッチ, ネットワークとカー ネルのパラメータ ○ HPjconfig ○ ○ OutOfBox ○ ○ カーネルパラメータ HP Java パフォーマンスツール 2 パフォーマンス問題の原因と対策 Java アプリケーションのパフォーマンスに影響を与える要因はたくさん考えられますが、 このセクションではその中でも代表的なものをいくつかピックアップして説明していきます。 それぞれの項目では基本的な解決方針についても触れられますが、効率的なチューニ ングを行うためには、その背後にある動作の仕組みを理解することが不可欠です。そう した技術的なバックグラウンドについても可能な限り説明します。 2-1 HP-UX カーネル・ パラメータ Java プログラムが動作するオペレーティング・システムを適切にセットアップすることは 極めて重要です。これはあらゆるチューニングを行う前にまず確認するべきことです。 Java 環境向けパラメータセット(初期インストール時の第一ステップとしてのパラメータセ ット)を自動的に設定するために HP では Java out-of-box とよばれるツールを提供してい ます。具体的にはこれをインストールすることにより、カーネル・パラメータを Java 環境に 適切なものへと変更し、カーネルを再構成し、システムのリブートを行います(同時にス タートアップ・スクリプトもインストール)。 http://www.hp.com/go/java ➟ hp products for Java 2 (PA-RISC) ➟ Java out-of-box tool for hp-ux 8 さらにアプリケーション環境に応じて、チューニング可能なパラメータに最適な値をセット するために利用できる便利なツールが HP より提供されています。 HPjconfig と呼ばれるこのツールはそれ自体が Java プログラムであり、以下の web サイ トより無償でダウンロードできます。 (java products for hp-ux) http://www.hp.com/go/java ➟ information library ➟ technical documentation ➟ HPjconfig configuration tool for hp-ux 11 and 11i 以下のようにして HPjconfig を起動します。 $ java –cp HPjconfig.jar:HPjconfig_data.jar HPjconfig このツールでは、コンピュータのモデルがチェックされた後、”Application Server”、”Web Server”、”WAP Server”などのアプリケーション・タイプが選択可能です。その後、HP-UX チューニング可能カーネル・パラメータの推奨値が提示されます。これらの値はファイル に保存することも可能です。スーパーユーザー、あるいはシステム・マネージャーは 図 2 HPjconfig の起動画面 SAM(システム管理ツール)を使用してオペレーティング・システムにこれらの値を適用 します。図 2 は HPjconfig の起動時の画面を示しています。このツールは現在の HP-UX に適用されているパッチが Java に最適かどうかもチェックします。 図 3 はあるアプリケーション・タイプに対するカーネル・パラメータの推奨値が示されてい ます。 HPjconfig の ”Recommended Tuned Value”に表示される値はあくまで推奨であることに 注意してください。コンピュータ上に複数のアプリケーション・プロセスが存在する場合や、 データベース・プロセスが走っている場合には、最適な HP-UX カーネル・パラメータはマ シンを共有している Java プログラムと他のプログラムとの間で調整される必要がありま す。 9 Java プログラムにおいて重要となる HP-UX カーネル・パラメータは以下のとおりです。 図 3 HPjconfig カーネル・パラメータの推奨値 max_thread_proc 一つのプロセスが持つことのできるスレッドの 最大数 maxdsiz プロセスのデータ・セグメントのサイズ maxfiles プロセスがオープンすることのできるファイル の最大数(ソフト・リミット) maxfiles_lim プロセスがオープンすることのできるファイル の最大数(ハード・リミット) ncallout I/O ペンディング・タイムアウトの最大値 nfile コンピュータ・システムでオープンできるファイ ルの最大値 nkthread コンピュータ・システムでサポートされるカー ネル・スレッドの最大数 nproc コンピュータ・システムで走らすことのできるプ ロセスの最大数 2-2 HP-UX ネットワーク ・パラメータ クライアントの接続がタイムアウトになったり、パケット・エラーの比率が高くなっ ている場合は、ネットワーク・パラメータ tcp_conn_request_max(デフォルトの値 は 20)が小さすぎる可能性があります。まず、アプリケーションの実行中に次の コマンドを使用して問題を調べます (あるいは、glance でパケット・エラーの比率 を観察できます)。 # netstat –p tcp 10 “connect requests dropped due to full queue”の等の値が増加している場合は、 次のコマンドで現在のパラメータ値を調べます。 # ndd –get /dev/tcp tcp_conn_request_max 次に アプリケーションを実行する前に、次のコマンドを発行してより大きな値を設 定します (HPjconfig を使って設定することもできます)。 # ndd –set /dev/tcp tcp_conn_request_max 2048 これによって問題が改善した場合は、リブート後もこの設定が有効になるように、 /etc/rc.config.d/nddconf 内でこの値を設定するようにしてください。 2-3 HP-UX および Java のパッチ 最適なシステムを構成する上で適切なパッチを適用することは避けてとおることはでき ません。オペレーティング・システムのバージョンや、システムにインストールされている Java のバージョンによって必要なパッチは異なりますが、HPjconfig ツールを使うことで、 現在のシステムに必要となるパッチを自動的に検出し、表示してくれます。 この情報は HP の web サイトより入手することもできます。 http://www.hp.com/products1/unix/java/infolibrary/patches.html 2-4 ガベージ・コレク ション Java プログラムはオブジェクトを必要とし、それらは処理を行うためにメモリを占有しま す。これらのオブジェクトは、プログラム実行時の最初に作成される場合もあれば、実行 している間に作成されることもあります。Java 言語において新しいオブジェクトが作成さ れると、ヒープと呼ばれる領域にそのためのメモリが割り当てられます。 以下のコマンドはデフォルトのヒープ・サイズで JVM を起動します(オプションを指定しな い)。 $ java ClassName (ClassName はアプリケーションのクラス名) 以下のコマンドはヒープの最大値を 240 MB に制限して JVM を起動します。 $ java –Xmx240m ClassName もしこの JVM が 240 MB 以上のメモリをオブジェクトに割り当てようとすると、JVM は”OutOfMemoryException”を発し、プログラムの実行を停止するでしょう。 Java プログラマは、C や C++ のプログラマと異なり、これらヒープを明示的に割り当て 解除、あるいは解放する必要がありません(C/C++ プログラマは ”delete” 操作を行う必 要があります)。 11 これはプログラマによってではなく JVM 自身によって行われます。JVM は、オブジェクト がプログラムのどこからも参照されていないことを確認し、そのオブジェクトがもう使用さ れていないということを決定します。この使われていないオブジェクトを取り除くことを“ガ ベージ・コレクション”と呼び、JVM の中では定期的に、独立した活動として機能します。 この頻度は、ヒープ・サイズの需要や他の様々な要素によって決定されます。 このメモリ管理手法の注目すべき点は、JVM が一度ガベージ・コレクションが必要だと判 断すると、他のすべてのスレッド(Java バイト・コード実行中、コード変換中、コンパイル 中を含む)が安全なポイントに置かれ、ガベージ・コレクションが行われている間、停止 されます。これはどのタイミングで実行されようとも Java プログラムのパフォーマンスに 決定的な影響を与えます。 この理由により、Java プログラムのパフォーマンスを分析する上でガベージ・コレクション の振る舞いを把握することは常に重要となります。 図 4 は 8 CPU のコンピュータ上で 1 つの JVM を実行している最中にガベージ・コレクシ ョンが発生した際の瞬間的な影響を示したものです。ここに見られるようにただ一つの CPU のみが高負荷状態となっており、これがガベージ・コレクションです。残りの CPU は ほぼ使用されていない状態となっています。 図 4 ガベージ・コレクション作動時の CPU 負荷状態 このため過度なガベージ・コレクションの発生は、Java プログラムのスローダウンの大き な原因となります。JVM にはヒープ関連のオプション(-Xms、-Xmx、-Xmn、 -XX:SurvivorRatio)を明示的に指定し、ガベージ・コレクションの発生をコントロールする べきです。しかし、まず最初の一歩は JVM で過度に発生しているガベージ・コレクション を見つけることです。 ガベージ・コレクションが煩雑に発生しているかどうかは Glance/gpm のメイン・ウインド ウをみることによって手掛かりを得ることができます。図 5 に見られるような突発的な CPU パターンは不要なガベージ・コレクションが発生していることを高い可能性で示して 12 います。また図 5 では、ユーザー・プログラム・タイムが時おり大きく落ち込んでおり、ガ ベージ・コレクションが CPU パワーを奪っていることが見てとれます。 Spiky CPU pattern 図 5 ガベージ・コレクション発生時の典型的な CPU パターン 2-4-1 ガベージ・コレクションの分析 –Xverbosegc ガベージ・コレクションの発生を確認することは HP-UX の Java では簡単に行うことができ ます。以下のように JVM のオプションとして”-Xverbosegc”を指定してください。 $ java –Xverbosegc:file=myfile.out ClassName これにより”myfile.out”というファイルにガベージ・コレクションのデータを保存することが できます。出力するファイルの名前は自由に変更できます。 Note:このオプション指定時の JVM におけるパフォーマンスの低下は非常に小 さなものです(ディスクに出力ファイルを書き込む影響を除く)。必要な場合は実稼動の システムに使用することも可能です。 以下のように、出力データ(上の例での”myfile.out”ファイルの内容)はそのままの状態 では読み取ることは困難です。 <GC: -1 21.040560 1 824 1 13369144 0 13369344 0 1703936 1703936 0 1206120 50331648 4793888 4793888 4980736 0.124737 > <GC: -1 24.040994 2 80 1 13369344 0 13369344 1703936 1703936 1703936 1206120 5058264 50331648 5032840 5032840 5242880 1.896397 > <GC: 2 28.602708 1 216 1 1109352 0 13369344 1703936 0 1703936 5058264 6780520 50331648 5242688 5242688 5242880 0.870284 > <GC: -1 45.500549 3 656 32 13369136 0 13369344 0 805256 1703936 6780520 6780520 50331648 8720800 8720800 8916992 0.055405 > 13 この出力ファイルには、ガベージ・コレクションのタイミングや範囲など様々な情報を示 す 19 の列(カラム)があります。それぞれのカラムの意味は、次のコマンドで確認するこ とができます。 $ java –Xverbosegc:help この出力ファイルを見やすく整形するために、以下の web サイトよ り ”processVerboseGC.awk” ファイルをダウンロードすると良いでしょう。 (java products for hp-ux) http://www.hp.com/go/java ➟ information library ➟ brochures and articles ➟ performance tuning java on hp-ux ➟ tools ➟ -XverboseGC (JVM profiling option) ➟ -XverboseGC script そして以下のコマンドを実行してください。 $ cat myfile.out |awk –f processVerboseGC.awk > output.txt このコマンドは次に示すような、はるかにユーザー・フレンドリーな出力ファイルを生成し ます。 GC: Full GC required - reason: Old generation expanded on last scavenge GC: Full 1.985317 s since last: 1.985317 s gc time: 96 ms eden: 1834928->0/3670016 survivor: 120576->0/262144 tenure: 2 old: 3204992->2356088/3928064 GC: Scav 2.190710 s since last: 0.205393 s gc time: 4 ms eden: 3669968->0/3670016 survivor: 0->120720/262144 tenure: 32 old: 2356088->2356088/3928064 これらのデータはプログラム実行中に発生した 3 回のガベージ・コレクションを順番に示 しています。それらが発生した時間は、プログラム開始から経過した時間として、”Full” あるいは ”Scav” の次に記述されます。 ”eden”や”survivor”、”tenure”、”old”といった言葉の意味がわからなくても、多くの情報 を読み取ることができます(これらの単語の意味はこの後で説明します)。 “Full” ガベージ・コレクション(GC)イベントは、”Scavenge” あるいは ”Scav” ガベージ・コ レクションと比べて常に時間がかかります。煩雑に”Full GC” が発生している場合(例え ば 1 分に 1 回かそれ以上)には、何らかの対策を講じる必要があります。 “Full GC required – reason : call to System.gc() or Runtime.gc()” このデータに上のような記述があった場合には、ユーザーあるいはライブラリの Java コ ードが明示的にガベージ・コレクションを呼び出していることがわかります。これは必ず 避けなければなりません。コードから System.gc() や Runtime.gc() の呼び出しを取り除く か、JVM オプションに ”-XX:+DisableExplicitGC” を指定することによりこの問題を解決することができます。 14 $ java –XX:+DisableExplicitGC ClassName “Full” あるいは “Scav” のすぐ後に続く数字はプログラムが開始された後の経過時間で す。この時間(単位は秒)はプログラムの実行に従って累積され、そのイベントがどの時 点で起こったのかを教えてくれます。 “since last” の後には、直前の GC が発生した後どのくらい時間が経過しているかを示す 数字が続きます。”gc time” は、この GC イベントが完了するまでに要した時間を意味し ます。これらの数字には特別に注意しなければなりません。一般的に正常な JVM は Scavenge GC より少ない回数の Full GC を実行します。Scavenge GC は 0.5 秒以上の 間隔をあけて分割して実行され、それぞれに 300 – 400 ミリ秒以上の時間がかかること はありません。 もし Scavenge GC が 1 分以上行われている場合は、Java バイト・コードを実行している スレッドが少なくとも 1 分は停止していることになり、パフォーマンスの問題を抱えている ことになります。この問題を解決するためには、ヒープ・メモリの使用法を JVM オプション (-Xms、-Xmx、-Xmn など)を指定することによって変更します。ヒープ・メモリのどの領域 が利用不足なのか、あるいは逆に利用されすぎているのかということを理解した後は、 これらの値を適切に変更するべきです。この点については次のセクションで解説しま す。 Full GC はヒープ領域のサイズに応じて数分かかることもあります。通常は、Full GC が 1 分以内に終了し、Scavenge GC と比較して少ない頻度で起こるように JVM を構成するよ うに心がけてください。Full GC が 5∼10 分毎、Scavenge GC は 5∼10 秒毎に発生して いるようであれば正常な JVM の振る舞いを示しています。 2-4-2 Java ヒープ・メモリの構造 このセクションではヒープ管理の詳細へと話を進めていきます。JVM のヒープ・メモリ全 体の中で、新しいオブジェクトと古いオブジェクトがどのように配置されるかを理解するこ とで、そのスペースがいかに有効に利用されているか、あるいはその逆であるかを判断 することができます。JVM オプション –Xms、-Xmx、-Xmn を使うことによって、こうした振 る舞いを変更することができます。 図 6 は JVM におけるヒープ・メモリのレイアウトを示したものです。 15 -Xmx -Xmn Eden SurvivorRatio = Eden/From = 8 From MaxTenuringThreshold (32) Eden From To NEW (16/2 MB) Tenured Objects OLD (48/4 MB) 図 6 ヒープ・メモリのレイアウト この図のように、ヒープ・メモリの中には 4 つのスペース、あるいは活動領域があります。 この世代別に分けられたガベージ・コレクションの設計目的は、短命のオブジェクトに対 しては ”NEW” 活動領域でその一生を過ごし、高速なアルゴリズムによってガベージ・コ レクションされるべきであるということと、長い間存在するオブジェクトに対しては “OLD” 活動領域に移ってもらい、低速なアルゴリズムによって発見され取り除かれるまでそこ に留まるべきであるということです。 ヒープ・スペースには他にも”Permanent”領域があり、ここにはロードされたクラ スの情報が置かれます。Permanent 領域のサイズは以下のオプションで指定で きます。 -XX:PermSize permanent 領域の初期サイズ -XX:MaxPermSize permanent 領域の最大サイズ Permanent 領域も含めたヒープ・メモリのレイアウトを図 7 に示します。 -XX:MaxPermSize Eden From To NEW (MAX 16MB) Tenured Objects OLD (MAX 48MB) Reflection Object Permanent (MAX 64MB) 図 7 Permanent 領域を含めたヒープレイアウト Permanent領域の詳細については(付録 B) “HP-UX HotSpot JVMのPermanent 領域について”を参照してください。 Scavenge GC は “NEW” 活動領域内で行われ、高速であることを目的としています。 Full GC は “NEW” と “OLD” の両方の領域をカバーし、そのため Scavenge GC よりは 遅くなります。 16 “NEW” 領域のデフォルトのスタート時のサイズは 2MB、デフォルトの最大サイズは 16 MB です。 “OLD” 領域のデフォルトのスタート時のサイズは 4 MB、デフォルトの最大サイズは 48 MB です。これらの値は以下の JVM オプションを用いて変更できます。 -Xms スタート時のヒープ・サイズ -Xmx 最大ヒープ・サイズ -Xmn “NEW” 領域のサイズ -XX:SurvivorRatio=<n> “Eden” エリア・サイズを”From” あるいは “To” エ リアのサイズで割った数。”From” と ”To” のサイ ズは同じ。 最初の 3 つのオプションについては、割り当てる領域のサイズをメガバイト単位の数字 で指定します。例えば –Xmn256m は”NEW” 領域に 256 MB のサイズ割り当てを指定 します。 一つの推奨すべき方法は、-Xmn の値を –Xmx の 1/3 に設定することです。より積極的 にチューニングすべき状況では、-Xmn の値を –Xmx の半分に指定することも可能です。 “NEW” 領域内の “Eden” のスペースは、新しいオブジェクトが作成された際に最初に配 置されるメモリ領域です。これは非常に重要なスペースです。というのも全てのユーザ ー定義オブジェクトはプログラム実行中の同じ時刻に作成されるからです。そしてそれら は長い時間存在することもありますし、そうでない場合もあります。一つの例として Panel という一般的なユーザーインターフェイス・クラスのオブジェクトを Java プログラムが作成 した場合を考えてみましょう。 Panel p = new Panel(); この新しいオブジェクトは 他の場所にコピーされるまで “Eden” スペースに存在します。 このオブジェクトがプログラムの中で長い間使用される場合は、”OLD” 領域に移動され ます。 2-4-3 Java ガベージ・コレクションのメカニズム このセクションでは Java ガベージ・コレクションの概要を説明します。図 8 に示すように、 通常 ”Eden” スペースは新しいオブジェクトが作成されるに従い、時間とともにどんどん 埋まっていきます。”Eden” が一杯になると、Scavenge GC が開始され、リファレンスが 存在するオブジェクトは “Eden” スペースから “To” スペースにコピーされます(図 9)。こ れは “To” スペースへの最初の移動なので図中には “1” と表記しています。 17 MaxTenuringThreshold=2 Ed From To New (16/2MB) Old (48/4MB) 図 8 新しいオブジェクトは Eden に “To” スペースは 2 つある“survivor” スペースのうちの 1 つであり、使用されているオブジ ェクトを一定の期間保持し、GC イベントを数回 “survive” するまで ”OLD” 領域へ移動さ せられるのを遅らせます。もう 1 つの “survivor” スペースは “From” です。 リファレンスを持たないオブジェクトはこの時点で ”Eden” スペースより取り除かれ、以前 占有していたスペースは利用可能になります(図 9)。このようにして不要なオブジェクト がガベージ・コレクションにより取り除かれます。 MaxTenuringThreshold=2 Eden From 111 1 To Old (48/4MB) New (16/2MB) 図 9 Scavenge GC(移動) MaxTenuringThreshold=2 Eden To 1111 To New (16/2MB) Old (48/4MB) 図 10 Scavenge GC(完了) Scavenge GC 完了後、”From” スペースは ”To” スペースへ、”To” スペースは ”From” ス ペースへと名前が交換されます。 時間が経過し、オブジェクトが作成されるに従い、再び “Eden” が一杯になり、図 11 に 示すように 2 度目の Scavenge GC が発生します。”Eden” スペース、および ”From” スペ ースにあるリファレンスのあるオブジェクトは、”To” スペースへと移動されます。”To” ス 18 ペースへの移動が 2 度目のオブジェクトは図中で “2” と記しています。図 12 は 2 回目 の Scavenge GC が完了した後の状態です。 MaxTenuringThreshold=2 Eden 2 21 1 To 11 From To New (16/2MB) Old (48/4MB) 図 11 2 回目の Scavenge GC(移動) MaxTenuringThreshold=2 Eden 2 21 1 To To New (16/2MB) Old (48/4MB) 図 12 2 回目の Scavenge GC(完了) このようにオブジェクトはそれらが参照されている間、Scavenge GC が発生する度 に ”From”スペースと ”To” スペースの間でコピーを繰り返します。これは “MaxTenuringThreshold” で示されるコピー回数の限界まで続けられます。図 12 には説 明を簡単にするために “MaxTenuringThreshold = 2” としていますが、デフォルトは 32 です。従って、オブジェクトが “NEW” 領域において最大 32 回の Scavenge GC( = 32 回の From-To 名前変換)まで “survive” することを意味します。それ以降はオブジェクト は ”tenured” となり、”OLD” 領域へと配置されます。この “OLD” エリアはプログラム実 行中、長い期間存在するオブジェクトのためのものです。 3 度目の Scavenge GC が発生すると、”From” 内のオブジェクトは再度 “To”スペースへ と移動することになりますが、3 度目の移動となるオブジェクトに関しては、この例で設定 されている ”MaxTenuringThreshold = 2” を超えるため、”To” スペースではなく、”Old” スペースへとコピーされることになります。この様子を図 13 に示します。 19 MaxTenuringThreshold=2 Eden From 2 2 11 1 33 To New (16/2MB) Old (48/4MB) 図 13 3 回目の Scavenge GC(移動) eden: 1834928->0/3670016 以上の説明により –Xverbosegc の出力をより理解することができるようになりました。出 力ファイル中の上記のようなエントリはそれぞれ、現在の GC イベント前に占有されてい た “Eden” スペースのサイズ(矢印の左側の数字)、イベント後の “Eden” 内の使用され ているサイズ(矢印の右側の数字)、”Eden” 全体のサイズ(”/” の右側の数字)です。よ ってこのエントリは以下のように解釈されます。 ► ► ► この GC が発生する前は、”Eden” スペースを 新しいオブジェクトが 1834928 バイト使用していた。 GC 後の ”Eden” の使用量が 0(矢印の右側)なので、この GC によりこれ らのオブジェクトは ”Eden” スペースより完全に取り除かれた(そしてどこ かに配置された)。 GC が終了した後、”Eden” の合計サイズは 3670016 バイト。 同じ手法が “survivor” と “old” にも適用できます。この場合、”survivor” という用語は “From” と “To” を合わせたものとして使われます。 Scavenge GC が起こるたびに “Eden” スペースが一掃されるのはガベージ・コレクション において通常の振る舞いです。しかし、もし “Survivor” (“From” と “To”) の数値が Scavenge GC 後に 0 になるようならば、何かおかしなことが起こっています。これはオブ ジェクトが十分に “From” と “To” の間を行き来せず、”Old” スペースにすぐに移されて いるということです。こうしたアクションが繰り返されると、”Old” スペースは短命オブジェ クトによりすぐに一杯になってしまいます。この症状はオーバーフローと呼ばれ、 MaxTenuringThreshold が低い(2∼10)ために起こる GC の連続した発生によって見つ けることができます。 オブジェクトを適切な期間、”New” エリアに保持する十分なスペースがない場合は、ま ず –Xmn オプションを用いて ”New” エリアの大きさを調節することができます。これによ り、全ての ”New” スペース(”Eden”、”from”、”To”)のサイズが拡張されます。以下のよ うに用います。 $ java –Xmn160m –Xmx480m –Xms480m ClassName 20 最大ヒープ・サイズ全体(-Xmx で指定)に対する ”New” エリア・サイズの推奨比は 1/3 から半分です。半分の場合はかなり積極的なチューニングとなります。 “Eden” のサイズに対する比率として “From” と “To” のサイズを調節することもできます。 以下のように JVM オプションの –XX:SurvivorRatio=<n> を使用します。 $ java –Xmn120m –Xmx480m –Xms480m –XX:SurvivorRatio=8 ClassName これにより”Eden” のサイズは “From” あるいは “To” のサイズの 8 倍となります(”From” と “To” のサイズは同じです)。上記の例の場合、”New” 領域は 96MB(8×12MB)の “Eden” スペースと、それぞれ 12MB の “From” と “To” に分割され、これらの合計は 120MB となります。 -XX:SurvivorRatio の値はデフォルトのままにしておくことが推奨されています。この値は JDK 1.3.1 では 8 です。 これらの対策は、Full GC の回数を減らすことと MaxTenuringThreshhold をできるだけ 32 に近づけるという観点に基づいて行われるべきです。この MaxTenuringThreshhold の値はガベージ・コレクションが進行するに従い調整されるため、プログラム実行中は 常に変化する可能性があります。低い値になるほど、オブジェクトは “From” や “To” ス ペースから “Old” 領域へ移りやすくなります。これは避けるべき状況です。 JVM オプション –Xverbosegc を指定し長い間実行すると非常にたくさんのデータが出力 されるので、MS Excel などの表計算ソフトに –Xverbosegc の出力を取り込むのも 1 つの テクニックです。表計算ソフト上で時間の経過とともに ”New”、”Old” 領域が増減する様 子をグラフに描きます。この方法については以下の Developer Solution Partner(DSPP) web サイトに詳しく書かれています。 http://www.hp.com/dspp 同様に、-Xverbosegc の出力をグラフ化するためには、MS Excel を利用する以外にも、 現時点ではベータバージョンですが java ベースのツール HPjtune を使用することもでき ます。 http://www.hp.com/products1/unix/java/java2/hpjtune/index.html ここでは簡単な例として Java のベンチマークプログラムの 1 つである SPEC JBB2000 で のヒープ・サイズの大きさの変更に伴う GC 後の ”Old” 領域の変化を見てみます。 図 14 はヒープ・サイズの調節なしに SPEC JBB2000 を実行した結果です。 21 GC が頻発し、Old 領域の急激な増加がみられます。これは New サイズが小さいため オーバーフローが起こり、Old 領域に Live Object が溢れている望ましくない状態と考え られます。また Full GC の実行のたびに Old 領域のサイズが不連続に大幅に減ってい ることが観察されます。このことから長い間存続することのない Live Object までもが Old 領域に移動してしまっていると推測できます( Full GC 後の Old 領域が定常的に増加し ているのは実行プログラムである SPEC JBB 2000 の特性によるものです)。 250.0E+6 Live Objects (bytes) 200.0E+6 150.0E+6 100.0E+6 50.0E+6 000.0E+0 0 200 400 600 800 1000 1200 1400 Tim e (seconds) 図 14 SPEC JBB2000 における GC 後の Old 領域のサイズ変化 (ヒープ・サイズ調節前) 図 15 は JVM のオプションによりヒープの調節を行った後のものです。図 14 の場合と比 較し、GC の回数も減少し、Old 領域の急激な増加もみられません。これはヒープの調節 (New 領域、Old 領域の拡大、SurvivorRatio の変更等) を行った結果、望ましい状態にな ったと考えられます ( Old 領域が段階的に増加しているのは実行プログラムである SPEC JBB 2000 の特性によるものです)。 22 250.0E+6 Live Objects (bytes) 200.0E+6 150.0E+6 100.0E+6 50.0E+6 000.0E+0 0 200 400 600 800 1000 1200 1400 Tim e (seconds) 図 15 SPEC JBB2000 における GC 後の Old 領域のサイズ変化 (ヒープ・サイズ調節後) 2-5 リソース競合 Java はプログラム内で簡単にスレッドや非同期プロシージャを利用できるプログラミング 言語です。これは言わば諸刃の剣であり、マルチ・スレッド・プログラミングに習熟してい ないプログラマの場合には問題が発生することが多々あります。 スレッドを無制限に作成するのはもちろん悪いやり方であり、行ってはいけません。多く のアプリケーション・サーバー(ミドルウェア・システム)では数千のスレッドを作成します。 HP-UX は 1 つのプロセス内に複数のスレッドをサポートしますが、このスレッド数はカー ネル・パラメータ max_thread_proc によって制限されます。この値は以下の “kmtune” コ マンドを実行することで確認することができます。 $ kmtune | grep max_thread_proc 幸運にも、これらのスレッドの振る舞いを実行中、あるいは実行後に観察することのでき る各種のツール(Glance/gpm、HPjmeter、”kill –3”を使うテクニック)が提供されていま す。これらについては後ほど詳述します。 23 Total Number of Threads Thread ID Active Threads JVM Threads 図 16 Java プログラム中のスレッド 図 16 は特定の Java プロセスを Glance/gpm を使って表示し、Java プログラムが実行中 に生成した全てのスレッドを考察している図です。画面の一番左の TID はそれぞれのス レッドに対するスレッド ID です(このスレッド ID は、後述するスレッド・スタック・トレースの lwp_id に対応します)。 JVM 自身を開始するために 11 のスレッドが必要だということを覚えておいてください。こ れらのスレッドはユーザー・プログラム・コードで必要となるスレッドとは別のものです。 Java プログラマがスレッドを使う上で気をつけなければならないことを以下にまとめま す。 ► オペレーティング・システムによって決められている制限を越えてスレッド を作成してはならない。 Java プログラム中で ”OutOfMemoryException” エラー、あるいはスレッド が多すぎるというメッセージを受け取ります。この問題の解決は簡単で す。HPjconfig ツールを使い、カーネル・パラメータ “max_thread_proc” の 値を確認し、SAM(管理ツール)を使って値を変更してください。 HPjconfig は HP Java web サイトより無償でダウンロードできます。 http:/www.hp.com/go/java ► 24 あるスレッドが、共通のリソースに対するロックを長い間保持することによ り、他のいくつか、あるいは全てのスレッドの処理ができない状態は避け なければならない。 図 17 はあるスレッドがリソースを保持しているため、他のスレッドの進行 が止まっている問題を示しています。 Control access to shared resources thread 1 Shared Resource thread 2 thread 3 Java monitors control access Monitor thread 1 thread 2 Shared Resource thread 3 図 17 複数のスレッドによりアクセスされるリ ソースの制御 ロックの競合は、どのくらいのスレッドがロックを獲得しようとしているか、どのくらいの頻 度でそれを行っているかによって決まります。競合が激しい場合は、有意義な作業を行 う代わりに、オブジェクト・モニタに入るのを待つことにスレッドの時間が費やされます。 HP-UX 上の JDK 1.3.1(あるいはこれ以降)では、-Xeprof オプションを使用して、ロック 競合に関する正確なデータを得ることができます。このデータは HPjmeter によって解釈 されます。 あるスレッド T1 がリソース A のロックを保持しています。リソース A はスレッド T2 で必要 となるリソースです。そしてスレッド T2 はリソース B のロックを保持しています。このリソ ースはスレッド T1 が必要としているリソースです。この場合、これらのスレッドは抜け出 せない状況に陥り、どちらかのスレッドがロックを開放しない限り永久に進展はありませ ん。 これらの問題は全てアプリケーション・ソフトウェアの設計に起因しています。それゆえソ フトウェアの構造を変更すること(再コンパイルを含む)により大部分の問題は解決され ます。 スレッドやロック競合の問題に対する最初の手掛かりは Glance/gpm の出力より得られ ます。図 18 は、ほとんどの CPU 時間が ”User” タイム(明るい色)ではなく ”System”タイ ム(暗い色)で消費されているシステムを示しています。正常なシステムはこの逆でなけ ればなりません。”User” タイムが CPU 時間の大半を占めるべきです。この時間がアプリ ケーション・コードの実行に費やされる時間です。 25 Low User CPU Time High System CPU Time 図 18 リソース競合が発生している証拠 これはスレッド、あるいはロック競合の問題が起こっていることを示すひとつの指標とな ります。ここで再び Glance/gpm を使い、システム上の個々の JVM プロセスによって呼 ばれているシステム・コールを見てみます。 ロック競合問題を抱えているプロセスでは図 19 のような画面が表示されます。 Glance/gpm の “Cumulative system call count” 欄が示すように、”sched_yield” や ”ksleep”、”kwakeup” などの特定の HP-UX オペレーティング・システム・コールが他 のコールと比べてかなり多くの回数呼ばれています。これらのコールは JVM のバージョ ンによって異なる場合もありますが、呼び出し頻度の高いコールとして覚えておくべきも のです。図 19 において ”send” と “recv” コールの呼び出し頻度を指している矢印は、こ れらのコールが “sched_yield” や “ksleep” と比べてかなり低い頻度で呼ばれていること を示しています。”send” や “recv” コールはネットワーク・プログラムにおいて主要な作 業を行っているので、これらのコールはもっと高い頻度で呼ばれるべきです。 もしここで完全にスレッド競合の問題を解決することができたなら(例えばポーリング・ス レッドやスレッド・プール・モデルなどでスレッド設計を再構築するなど)、”send” や “recv” コールはもっと煩雑に呼ばれることになるでしょう。 このような問題は、ネットワーク・トラフィックをオープンするためのソケットそれぞれが 1 つのスレッドとして使用されているプロジェクトで発生しました。数百、数千のネットワー ク・コネクションが同時に確立している状況では、スレッド競合によるかなり高いオーバ ーヘッドが見られることになります。このプロジェクトにおける問題は HP Poll API を使って インプリメントすることで修正されました。Poll API は少ないスレッドを使用するアプリケー ション設計のひとつのパターンです。この内容については本書の範囲を超えるので省略 します。Java SDK 1.4 では非同期 I/O 環境においてスレッドの制御が容易になるサブシ ステムが実装される予定です。 26 Pattern for contention: HIGH sched_yield() and ksleep() 図 19 スレッド競合を示す高いシステム・コール呼び出し数 2-5-1 スレッド・スタック・トレース kill –3 JVM でスレッドを分析するこの方法は非常にシンプルでしかも非常に強力です。動作し ている Java プロセスに対して、パラメータ “-3” あるいは “-s SIGQUIT” を用いて “kill” コ マンドを実行すると、JVM の実行には影響を与えずに、その瞬間に存在しているスレッド の全ての情報を詳細なダンプとして得ることができます。Glace/gpm を使用するか、あ るいはシンプルに、 # ps –ef | grep java とコマンドを打つことで対象とする JVM プロセスを特定することができます。そして以下 のコマンドを実行することでこのプログラムのスレッド・データのフルダンプを標準出力に 出力します。 # kill –3 3493 (ここで 3493 は JVM の PID) 標準出力をファイルに保存するには、JVM 起動時にリダイレクトを行います。上記の “kill” コマンドはスーパーユーザーの権限が必要なこともあります。JVM によって生成さ れるデータ(サイズが大きい場合にはファイルにリダイレクトする必要があるかもしれま せん)は次の例のようなものです。 "Worker Thread 17" prio=9 tid=0x1310b70 nid=41 lwp_id=14165 suspended [0x1194d000..0x11948478] at fields.FieldPropertiesLibraryLoader.forClass(FieldPropertiesLibraryLoader.java:67) - waiting to lock <0x3ca45848> (a java.lang.Object) at fields.FieldsServiceImpl.getFpl(FieldsServiceImpl.java:75) at fields.FieldsServiceImpl.getFpl(FieldsServiceImpl.java:64) at base.core.BaseObject.getFpl(BaseObject.java:2930) at base.core.BaseObject.getFieldProperties(BaseObject.java:2661) at core.BaseObject.getFieldProperties(BaseObject.java:2670) at fields.FieldProperties.getFieldsInGroup(FieldProperties.java:1157) 27 at fields.FieldProperties.getFieldsInGroup(FieldProperties.java:1107) 上の例は、ある特定のスレッド(lwp_id —lightweight process identity--- が 14165) が サスペンドの状態にあり、オブジェクト(16 進数アドレスで特定)のロックを獲得しようと 待っている様子を示しています。これは大きなダンプのほんの一部のスナップショットで すが、興味深い情報を提示しています。この出力ファイル中で探そうとしているのは、他 のスレッドが必要としているオブジェクトのロックを長期間保持しているスレッドです。こ れにより JVM 全体が著しくスローダウンし、深刻な問題となります。(Java スタックトレー スによるリソース競合検出の詳細については付録 D を参照してください。) 問題のあるプログラム実行中は何回でも “kill –3 <JVM プロセス ID>” を繰り返すことが できます。しかし多数のスレッドを含むプログラムを対象とする場合には、莫大な量のデ ータが生成されることもあります。問題を探すためにこのデータを綿密にチェックするこ とは非常に大変です。こうした作業を簡素化するために、次に述べる HPjmeter ツールが 使用可能です。 2-5-2 スレッド・ロック問題 – HPjmeter の使用 JVM のスレッドの振る舞いを分析するシンプルなアプローチは HP 特有のオプションであ る –Xeprof を指定してプログラムを最後まで走らせ、その後その出力を HPjmeter ツール を使用して解析することです。 JVM 上で動作するプログラムが正常な方法で終了できる場合は(つまり System.exit()を 使うか、他の段階的なシャットダウン手法によって)、以下のような拡張プロファイリン グ・オプションを指定して JVM を起動してください。 $ java –Xeprof:file=myfile.eprof ClassName ここで出力されたファイル(例では myfile.eprof)を分析するためのツールが HPjmeter プ ロファイリング・ツールです。このツールは以下よりダウンロード可能です。 http://www.hpjmeter.com このツール自体が Java で書かれており、JVM がサポートされているどのプラットフォー ムでも実行することができます。このツールは–Xrunhprof によって出力されたファイルを 読むこともできますが、HP の JVM 特有のオプションである –Xeprof からの出力ファイル を利用することにより多くのパフォーマンスに関する情報を得ることができます。 HPjmeter を起動するコマンドは以下に示されているとおりです。 $ java –cp /opt/hpjmeter/HPjmeter.jar HPjmeter 28 この無償のツールはスレッド生存期間に関するものだけでなく、多くのメトリックを備えて います。これらのメトリックは、ダウンロード・サイトのチュートリアルに詳細が記述されて います。このセクションではスレッドの分析に焦点を絞ります。HPjmeter は、JVM 実行中 のスレッドの状態と生存期間をグラフィカルなイメージとして表示します。-Xeprof の出力 をツールに取り込み、”Metric” メニューの Thread Histogram を選ぶと図 20 のような画 面を見ることができます。 Sort Threads Histogram Double-click for Pie Chart Thread 0 CPU 図 20 HPjmeter によるスレッドの状態と生存期間の表示 この画面で一度に全てのスレッドの生存期間を確認することができます。それぞれのス レッドを表すバーをダブル・クリックすることで、問題のある 1 つのスレッド、あるいはスレ ッド・グループを更に分析することができます。図中のスレッド 0 はその存在期間のうち 77.1%を CPU に費やしています。これは正常な証拠です。このスレッド時間の残りのほ とんどである 21.4%はプロファイラーのオーバーヘッドに使われていると報告されており、 実行待ちやロック競合で使われている時間はほとんどないことがわかります。 HP の Java SDK 1.3.1 以降と HPjmeter 1.2 以降を用いることにより、Java プログラムの スローダウンの原因となるスレッド競合について極めて正確な情報を得ることができま す。 HPjmeter はスレッドの他にも、多くの場面における分析に威力を発揮します。CPU を多く 消費するメソッドや、その実時間などもこのツールを使って追跡することが可能です。こ れについては以降の「リソースを大量に消費するメソッド・コール」で解説します。 2-5-3 スレッドの優先度 Java プログラム・スレッドは Java バーチャル・マシンによって HP-UX ライトウエイト・プロ セス(LWP)と呼ばれるオペレーティング・システムのスレッドへとマッピングされます。 HP-UX 内でスケジューリングされる単位は、個々のスレッドです。それぞれのスレッドは、 29 例えば Glance ツールの ”Thread List” 機能を使えば一意な LWP ID と共に表示させるこ とができます。 最初はそれぞれのスレッドの優先度は、オペレーティング・システム上の全てのユーザ ー・プロセスで共通の値が選ばれます。スレッドが CPU で実行されるに従い、正常な状 態では CPU 時間が割り当てられるたびにその優先度は低くなっていきます。オペレーテ ィング・システムは優先度を基にスケジューリングを決定するので、このスレッドは次に 他のスレッドと比較される際に不利となります。 ある特定の Java プロセスがそのコンピュータ上で唯一の重要なユーザー・プロセスであ る場合は、スーパーユーザーによって起動時、あるいは実行中により高い優先度を与 えることも可能です。これは “nice”、”renice” コマンドを使うか、Glance/gpm の ”renice” 機能を使って行うことができます。 nice コマンドの使用例を以下に示します。 # nice -–20 java ClassName (”-“記号が 2 つあることに注意) renice コマンドでは、”ps –ef” コマンドや Glance/gpm の Process list 画面によって得られ る Java プログラムのプロセス ID が使用されます。次の例で使われている Java プロセス ID は 1234 です。 # renice –20 1234 (”-“記号は 1 つ) nice および renice コマンドを実行する際は十分に注意してください。これらのコマンドは スーパーユーザーでログインしている間のみ使用できます。またこれらのコマンドは優 先度が変更されたプロセス内の全てのスレッドの優先度を変更します。 上記のようなコマンドを実行することにより、同じコンピュータ上の他のプロセスは相対 的に優先度が低くなり、以前と比べて CPU 時間の割り当てが少なくなります。これらはト レードオフの関係であり、特定の環境では有用な方法です。 2-6 リソースを大量に 消費するメソッド ・コール Java プログラムでは時々、少ない数のプログラム・メソッドが、そのプログラムのほとん どの時間とリソースを消費する場合があります。アプリケーション設計において、これは 明らかにチューニングの対象となるものです。 特定のメソッドには、アプリケーションのデザインやソースコードを変更することなくチュ ーニング可能なものがあります。まず最初のステップはこれらのメソッドを見つけ出し、 そのパフォーマンス特性を把握することです。HPjmeter はこうした分析に最適なツール です。 30 HPjmeter には Java が動作可能な環境全てにおいて無償で利用できるという特筆すべき 利点があります。更に HPjmeter に必要となるデータを生成する JVM オプション –Xeprof は、プロファイリングを行うプログラムへの影響ができるだけ小さくなるように設計されて います。 図 21 HPjmeter による Java プログラムのメソッド呼び出し 回数の表示 HPjmeter の起動方法は以前に示したとおりです。図 21 は HPjmeter において呼び出し 回数が最も多いメソッドを表示させた画面です。 この図より、このプログラムで呼び出されているメソッドの中で最も回数が多いのが Mark.SimSearch.bel()メソッドであることが明らかとなります。 ”bel()” メソッドは呼び出し回数で他のメソッドを大きく引き離しています。HPjmeter の他 のメトリック、例えば “Exclusive CPU Method Time” などを使用することにより、CPU サイ クルや他のプログラム・リソースを実際どれくらい消費しているかを知ることもできます (図 22)。その後、このボトルネックを取り除くために、このメソッドの設計や使用方法を変 更します。 31 あああああ 図 22 HPjmeter による Java プログラムのメソッド CPU 時間の表示 ある特定のケース、特に Java プログラムで Date クラスのオブジェクトが絡んでいる場合 は、ソースコードを書き換えることなくメソッドをより高速にできる可能性があります。 Java プログラムの中で Date オブジェクトが非常に多く使われている場合、メソッド “currentTimeMillis”、あるいは “getTimeOfDay” が HPjmeter のメソッド呼び出し回数の上 位に見られるはずです。 Date オブジェクトは JDBC を使用してデータベースからレコードを読み込んだり、データベ ースへ書き込んだりする場合に煩雑に使用されることがあります。 これがプログラムのパフォーマンスに深刻な弊害をもたらしていることがはっきりした場 合には、問題を解決するために次のオプションを使用することができます。このオプショ ンは、プログラムを実行している JVM より呼ばれている date/time 関数(OS 内ではアト ミックな処理)の影響を小さくする効果を持ちます。 $ java –XX:-UseHighResolutionTimer ClassName 2-7 メモリ・リーク Java 開発において最も注意すべきことのひとつは、メモリ不足によってプログラムが停 止する可能性です。これは “java.lang.OutOfMemoryException” などのエラー・メッセー ジにより顕著になることもありますが、プログラムの停止というあまりユーザーに親切で はない形で表れることもあります。 Java を使用しているプログラマは、ガベージ・コレクションのセクションで解説したような 方法でオブジェクトにメモリを割り当てます。理論上は、オブジェクトが使われなくなると そのメモリは自動的に開放され、プログラマが心配することはありません。 しかし、オブジェクトに対する全てのリファレンスが取り除かれない限り(”null” のセット、 あるいはスコープ外)、JVM はそのオブジェクトが再びプログラム実行中に使用されると 32 思い込みます。これはメモリ・リテンション問題と呼ばれています。C++ などの他の言語 でのメモリ・リークとは概念的に大きく異なります。 C++ でのメモリ・リークは以下に示すようなパターンに従ったコードによって起こります。 (プログラムの一部) ptr = new LargeObjectType(); // where ptr is a pointer of a correct type // The object is used for some purpose, then it is finished with. ptr = null; // The programmer has not used “delete ptr” to free space before setting ptr to null Java では問題は異なり、オブジェクトに “null” リファレンスをセットすることはプログラム がそのオブジェクトの使用を終えたことを意味するのでこれは良い作法となります。更に Java プログラマは、”free” や “delete” などのオペレータがないのでオブジェクトに割り当 てられたメモリを明示的に開放する手段を持ちません。実際、Java ではメモリ・リテンショ ン問題は、オブジェクト使用後にプログラマがリファレンスに “null” をセットすることを忘 れることに起因しています。 もしこの ”null” をセットし忘れたオブジェクトが他のオブジェクトを連鎖的に参照している 大きなオブジェクトであった場合、これらのオブジェクトに占有されているメモリ領域は、 それらがスコープ内にいる間は利用不可能になります。長い間プログラムが実行され、 こうした意図しないオブジェクトの振る舞いが繰り返されることで、プログラムのメモリが 使い果たされます。これは JVM を予期しないときに突然終了させる原因となります。 Java のメモリ・リテンション問題は、こうした腹立たしいコードを修正すること以外には一 般的に解決することはできません。この修正については本ドキュメントでは触れません。 Java プログラマはこれらの問題を早期に、開発段階での発見を目指して HPjmteter や JProbe プロファイラ・ツール(Sitraka Corporation)が有効です。HPjmeter によるメモリ・リ テンションについては次のセクションで述べます。JProbe の詳細については以下のサイ トを参照してください。 http://www.sitraka.com メモリ・リークやメモリ・リテンションを発見することは初心者にとっては難しい問題です。 この目的には、Glance/gpm ツールの “Memory Regions” 画面が大変役に立ちます。対 象となるプロセス(Glance/gpm では JVM は通常 “java”という名前でリストに表示されま す)を選び、そのプロセスで使用されている全てのメモリ範囲のレポートを表示させてく ださい。これにより図 23 のような画面が表示されます。 図 23 では、Data RSS(Resident set size)と Data VSS(Virtual set size)の値が C プログラ ム・ヒープへのメモリ割り当てを示しています。Other RSS と Other VSS、そして Private RSS サイズはこのプログラムで Java ヒープに割り当てられているメモリのサイズを意味し ます。これらの領域のうちひとつにでも継続的な増加が見られる場合は、この Java プロ グラムでメモリ・リテンション問題が存在すると考えてまず間違いありません。 33 C Heap Java Heap 図 23 プロセスが使用しているメモリの表示 多くの Java アプリケーションは背後で C/C++プログラムを呼び出しています。メモリ・リ ークやメモリ・リテンション問題が Java プログラムの裏に潜んだ C/C++プログラムに起 因しているということはよくあることです。 このように、この問題の性質上、Data RSS や VSS サイズの振る舞いを分析することが重 要となってきます。 対象となるプログラムで現象が発生するまでに長い時間がかかる場合には、上記のよ うに画面を監視し続けるのはほぼ不可能かもしれません。その場合には、Glance/gpm ツールキットの ”adviser mode” が便利でしょう。これはこのツールのバッチ・モードであ り、集めたデータをファイルに出力し、後ほどの分析に役立てることができます。 以下のコマンド例では、Glance/gpm は 5 秒毎にサンプルを採り、adviser_commands フ ァイル中に書かれたコマンドを使用します。 $ /opt/perf/bin/glance –adviser_only –syntax adviser_commands –j 5 adviser_commands ファイルには以下のような書式が用いられます。 PROCESS LOOP { if proc_proc_name == "Java" then { PRINT proc_proc_name|8|0, proc_proc_id|8|0, proc_cpu_total_util|8|2, proc_cpu_nnice_time|8|2, proc_mem_virt, proc_mem_res, proc_thread_count, proc_io_byte_rate } } 以下は、Glance を adviser_mode で走らせた際の出力例です。 34 ----- 20:15:37 (proc name, pid, cpu, negnice cpu, VSS, RSS, thread #, I/O) Swap space utilization: 42% nfile utilization: 6% bin 6390 93.33 0.00 787.9mb 389.7mb 2438 0.0 bin 6370 88.66 0.00 788.9mb 308.3mb 2444 0.0 ----- 20:15:38 (proc name, pid, cpu, negnice cpu, VSS, RSS, thread #, I/O) Swap space utilization: 42% nfile utilization: 6% bin 6390 80.00 0.00 793.8mb 389.7mb 2482 0.0 bin 6370 74.66 0.00 794.7mb 308.3mb 2488 0.0 […] VSS INCREASING! メモリ・リークの検出には Glance 以外にも、-Xverbosegc のデータ解析、HPjmeter (メニュ ー[Metric]→[Residual Objects(counts)] 等をチェック)、gdb(gnu debugger) の利用等が 有効な場合もあります。 2-8 HPjmeter を使った メモリ・リテンション の解析 この節では、HPjmeter を使ってメモリ・リテンションの解析を行う例を紹介します。 HPjmeter でプログラムの終了時に GC を行い残存オブジェクトの様子をみます。プログ ラムの終了は以下のようにするのがよいでしょう。 System.gc() System.runFinalization() System.gc() System.exit() java 実行時のオプションには –Xrunhprof:heap=all,cutoff=0 を追加しておきます。 また、ある期間の残存オブジェクトの増減の変化を知りたい場合は、2 度の実行の結果 を比較する HPjmeter の Compare 機能を利用できます。たとえば、ある 5 分間の間の残 存オブジェクトの変化を知りたい場合は、まず、プログラムを実行し、対象の期間の前ま でのプロファイルデータをとります。その後、もう一度プログラムを対象期間の最後まで (1 回目の実行より 5 分間長く)のプロファイルデータをとることにとります。その 2 つのプ ロファイルデータの差を Compare 機能で比較することにより対象となる 5 分間の残存オ ブジェクトの増減の状況を知ることができます。 以下、簡単な例で HPjmeter で 1 時間実行の結果と 2 時間実行の結果を比較し、その間 の 1 時間の残存オブジェクトについて解析します。 1. 1 時間実行と、2 時間実行のプロファイルデータを HPjmeter でロードします。 (メニュー[File]→[Open]でそれぞれのファイルを連続的にオープンします。) 35 2. メニュー[File]→[Compare]を選択すると 2 つのデータの概要が表示されます。 1時間実行 2時間実行 3.メニュ-[Metric]→[Residual Objects(Count)]を選択します。 ここで Authorise オブジェクトに注目してみます。 まず後の検索のために Authorize オブジェクトの行をクリックし、マークしておきます。 メニュー[Edit]→[Mark to Find] クリック 対象期間(1時間実行と2時間実 行の間)で8017ものAuthorise オ ブジェクトの増加 36 4.さきほどマークした Authorise オブジェクトについて 2 時間試行のデータをもとに解析し ます。 まず、 2 時間試行のプロファイルデータをロードします。メニュー[File]→[hprof_2hr.txt] 5.[Metric]→[Reference Graph Tree]を選択します。 37 6.先ほどの Authorize オブジェクトの参照を確認するため検索を行います。 (メニュー[Edit]→[Find]を選択します) 検索結果を一度に全て表示するか、どれか 1 つを表示するかを選択するようにダイアロ グが現れますが、今回は数が多いのでどれか 1 つ[Find Any]を選択します。するとある 参照テーブルの 7117 番目の要素として見つかりました。 7.テーブルの参照もとについて確認するために、同じテーブル 1 番目の要素について参 照を確認します。1 番目の要素のツリーを展開すると、Authorise オブジェクトへの参照 があることが確認できます。 また要素 1 のすべてのツリーを展開することにより他への参照なども確認できます。 38 8. HPjmter 1.2.1 からはメモリ・リテンションの疑いがあるオブジェクトを探す機能が追加 されました。(この機能では、経験的に参照が 1 つのみのオブジェクトをメモリ・リテンショ ンの疑いがあると判断しています。これはかならずしも、期待する結果とはならない場合 もありますが、とりかかりとしてはよいでしょう。) メニュ-[Guess]→[Memory Leaks]を選択します。 (ここではメモリ・リテンションをメモリ・リークと表現しています。) メモリ・リークの疑いがあるオブジェクトの Object ID とバイト数が、表示されます。 先ほどの 7.で注目していたオブジェクトと一致していることがわかります。 9. このオブジェクトがメモリ・リテンションの原因の疑いが高いことがわかりましたので、 この後はこのオブジェクトに対象を絞ってメモリリークの解析(プログラム的、論理的な 解析も含めて)を行うことになります。 このように HPjmeter を利用してメモリ・リテンションの解析を効率的に行うことができま す。 39 2-9 ベンチマークと Hot Spot ベンチマークはしばしばチューニングの効果を比較するために使用されます。しかし、 様々なベンチマークのパフォーマンスを分析してきた経験によると、特定のベンチマーク は誤解を生む原因になる場合があります。例えば、デフォルト JVM である Hot Spot を使 って実行しても、使わない場合より遅くなったように見えるなどです。 このセクションでは、Hot Spot コンパイラが何故パフォーマンスを向上させることができな いのか、それを改善するためにはどのようにすればよいのかについて解説します。 まず以下のプログラムを見てください。このプログラムは、Hot Spot 技術の恩恵を全く受 けていません。 public class SimpleBenchmark { public static void main(String[] argv) { int value=0; // Record the start time. long start= System.currentTimeMillis(); // Repeatedly executes feature to measure performance. for (int i=0; i<100000000; i++) { // Replace line with your favorite computation. value +=i; } // Record the finish time. long finish= System.currentTimeMillis(); } } // Now report how long test ran. System.out.print ("Time spent = " + Long.toString(finish - start) + " ms\n"); このプログラムをコンパイルし、Hot Spot JVM を使って実行すると以下のような結果を得 ることになります。 $ /opt/java1.3/bin/java SimpleBenchmark Time spent = 24168 ms 試しに同じプログラムを古い JVM で実行し、結果を比較してみます。Hot Spot を JIT (Just-In-Time)コンパイラに切り替えるために –classic オプションを指定します。 $ /opt/java1.3/bin/java –classic SimpleBenchmark Time spent = 911 ms Hot Spot JVM(動的な最適化)は、JIT を使った JVM よりも 25 倍も遅いという結果が導か れました。 この結果を更に考察し、コンパイルを一切行わない実行ではどのようになるかを見てみ ましょう。HP-UX SDK 1.3 バージョンでは、-Xint オプションを指定することで、コンパイル 40 が OFF となり、単純なバイトコードのインタプリタ形式となり HotSpot インタプリタを呼び 出します。 $ /opt/java1.3/bin/java –Xint SimpleBenchmark Time spent = 24100 ms Just-In-Time コンパイルを使用することで、インタプリタと比べて約 25 倍の高速化となっ ており、JIT コンパイラの威力を証明しています。残念ながらこれら JIT によるパフォーマン スの結果は、Hot Spot JVM のものよりも良い結果を示しています。この結果については、 クラシック(JIT)と Hot Spot それぞれが、何をどのような時にコンパイルするのか、また両 者の働きがどのように異なっているかを理解することで説明できるでしょう。 クラシック インターナル・コンパイラ。しばしば Just-In-Time(JIT)コンパイラとも呼ばれる。繰り 返し呼ばれるメソッドや、ループを含むメソッド の全てをコンパイルする。このコンパイラは最 適化はほとんど行わないため、インタプリタ方 式よりは高速だが、高度に最適化された C コ ードと比べると遅い。 Hot Spot 同じくインターナル・コンパイラ。Java バイトコ ードの一部を高度に最適化されたマシン語命 令に動的に変換する。それぞれのアプリケー ションに対して最も使用頻度の高い部分を特 定するため、このコンパイラのオーバーヘッド は JIT よりも大きい。プログラムの「Hot Spot」が 特定された後、これらの領域のコードはコンパ イル・最適化される。 Hot Spot の現在の設計は、長期間実行されるサーバー側アプリケーション向けのもので す。ほとんどのアプリケーションは実行時間の大部分を一部の限られたコードの実行の ために費やしています。これは一般的にプログラム・コードの 20%が実行時間の 80%を 占めることから、80/20 の法則と呼ばれています。Hot Spot は最初アプリケーションをイ ンタプリタ・モードで実行し、「Hot Spot」の分析および最適化を行います。この最適化に はパフォーマンスに大きな影響を与えるメソッドのコンパイルやインライン展開が含まれ ます。「Hot Spot」が特定され、最適化された後、インタプリタ方式によるバイトコードの実 行から、コンパイルされたコードの実行へと処理を切り替えます。長時間実行されるアプ リケーションは、その分コンパイルされたコードを実行する機会も多いため、Hot Spot の 恩恵をより多く受けることができます。しかし、こうしたプログラムは Hot Spot の分析と最 適化のために一時的にパフォーマンスに悪影響を与えることにもなります。 先ほどのプログラムではループで多数の繰り返しが実行されているので、Hot Spot によ るメソッドのコンパイルが行われます。しかし、ループがメソッド内の一部であり、実行中 にメソッドの再呼び出しがないため、通常はコンパイルされたコードの実行への処理の 切り替えが行われず HotSpot の恩恵をうけることがありません。 そのような場合は、-XX:+UseOnStackReplacement オプションを指定して on stack replacement 機能を有効にすることによりパフォーマンスをあげることができます。この オプションを指定することによりメソッド内の一部のループの繰り返しが実行されている 最中でも、コンパイルコードの実行に切り替えが行われるようになるためです。ただし、 この機能を有効にするためには現時点ではパッチ ( HPUX11.0:PHKL_24943, HP-UX11i 41 PHKL_24751) が必要です。また on stack replacement 機能を有効にするのと同時に、コン パイラのセーフポイント機能も有効にする必要があります (-XX:+UseCompilerSafepoints)。詳細は SDK 1.3.1.02 以降のリリースノートを参照してく ださい。 このオプションを指定して先のプログラムを実行することにより以下の結果を得ることに なります $/opt/java1.3/bin/java –XX:+UseCompilerSafepoints -XX:XXUseOnStackReplacement SimpleBenchmark Time spent = 42 ms on stack replacement 機能を有効にすることにより高速化されていることがわかります。 ベンチマークの中で比較的短い時間で終了するものを「マイクロ・ベンチマーク」と呼び ます。最適化が開始される前にベンチマークが終わってしまう場合、マイクロ・ベンチマ ークはパフォーマンスの良い指標とは言えません。こうしたベンチマークは Hot Spot が 想定しているアプリケーションとは異なるものです。実際これらのベンチマークでは、実 行時間に加えて分析・最適化にも時間を費やしているにも関わらず、その効果がほとん ど得られないため、パフォーマンスは悪くなる傾向にあります。結局、短時間しか実行さ れないアプリケーションにとっては、Hot Spot 技術はコードをコンパイルする前に分析・ 最適化を必ず行わなければならないため、パフォーマンスに弊害をもたらすものでしか ありません。しかし幸いなことにマイクロ・ベンチマークは世の中のアプリケーションを代 表するものではありません。 オリジナルのプログラムを少し変更することで、Hot Spot の効果を得ることができます。 これは、測定される部分を繰り返し呼ばれるメソッドに移すことで実現します。コードは 以下のようになります。 public class HotSpotBenchmark { public static void runTest() { int value=0; // Repeatedly executes feature to measure performance. for (int i=0; i<100000000; i++) { } } // Replace line with your favorite computation. value +=i; public static void main(String[] argv) { // Run benchmark multiple times. This will allow us to // see when HotSpot begins executing compiled code. for (int i = 0; i < 8; i++) { // Record the start time. long start= System.currentTimeMillis(); // Run benchmark test. runTest(); // Record the finish time. long finish= System.currentTimeMillis(); // Now report how long test ran. 42 } } } System.out.print ("Time spent = " + Long.toString(finish - start) + " ms\n"); このプログラムをコンパイルし、Hot Spot JVM で実行すると以下のような結果を得ること ができます。 $ /opt/java1.3/bin/java HotSpotBenchmark Time spent = 23372 ms Time spent = 23400 ms Time spent = 23372 ms Time spent = 11 ms Time spent = 11 ms Time spent = 11 ms Time spent = 11 ms Time spent = 11 ms 最適化されたメソッドを実行する前にインタプリタ形式でそのメソッドを 3 回実行している ことに注目してください。一般的に Hot Spot がメソッドを特定しコンパイルするのには 1、 2 分を要します。結果からわかるように、最適な変更を加えることでクラシック、JIT コンパ イラを大幅に上回るパフォーマンスを得ることができました。80 倍以上の高速化です。 ベンチマークを作成する際には、それがアプリケーションの重要な部分を含むかどうか だけではなく、全体のアーキテクチャを模倣できているかどうかを確認してください。Hot Spot の効果をあげるためには、そのベンチマークが十分に長い間実行され、「Hot」なメ ソッドが何回も呼ばれなければなりません。 2-10 その他の JVM オプション これまでに出てきた以外の JVM のオプションでパフォーマンスに関連するものをいくつ か挙げておきます。(jvm のバージョンによってサポートしているオプションの差異があり ますのでご注意下さい。) -server 長時間稼動するサーバー・アプリケーショ ン用に調整された Hot Spot VM を実行しま す(デフォルト)。 -client 短時間稼動する GUI アプリケーション用に 調整された Hot Spot VM を実行します。 -Xincgc インクリメンタル・ガベージ・コレクタを有効 にします(デフォルトでは無効)。インクリメ ンタル・ガベージ・コレクタは、プログラムの 実行中のガベージ・コレクションによる一時 停止を減少させます。ただし、全体のパフ ォーマンスは 10%程度低下します。 -XheapInitailSizes ヒープに関するパラメータ値の一覧を表示 します。 43 -XX:+ForceMmapReserved lazy swap を行わず、 JVM に設定されてい るページ・サイズを有効にします。大きなヒ ープ・サイズを利用するときに、TLB thrashing を防ぐために -XX:+ForceMmapReserved オプションが有 効な場合があります。 -Xoptgc 主に短命オブジェクトを扱うアプリケーショ ンのガベージコレクションを改善します。 -XX:+AggressiveHeap JVM の限界までメモリを利用します。この オプションは-Xmx や-Xms といったヒープサ イズを指定するオプションと一緒には使え ません。 その他にも FastSwing と呼ばれる機能を使用することによって、リモート X サーバーで実 行される Swing アプリケーションのパフォーマンスが大幅に向上します。リモート X サー バーには、X 端末、Exceed や Reflection X のような PC X サーバー、およびリモートの UNIX ワークステーションが含まれます。この機能を使用するには、次のように指定して java か appletviewer を実行します。 $ /opt/java1.3/bin/java -Dhp.swing.useFastSwing=true MyApp または $ /opt/java1.3/bin/appletviewer –J-Dhp.swing.useFastSwing=true applet.html この機能はリモート・ディスプレイにのみ使用することをお勧めします。ただし現時点で は以下のような注意が必要です。 ダブルバッファリングされた Swing コンポーネントは、FastSwing 機能が有効になってい ると Graphics2D 処理を実行できません。この処理を実行すると、次の例外が発生しま す。 java.lang.ClassCastException: sun.awt.motif.X11OffScreenImage at BezierAnimationPanel.run (BezierAnimationPanel.java:223) at java.lang.Thread.run(Unknown Source) 3 まとめ このホワイトペーパーで記述されたテクニックとツールは、Java プログラムのより良いパ フォーマンスを達成するための手助けとなります。パフォーマンス・チューニングを行う 際は、最初に得られた悪い結果に落胆してはいけません。こうした悪い結果の背後に隠 れた問題を解決し、良い結果を得ることは可能です。 44 プログラムのパフォーマンスに影響を与えるあらゆるアクションを行う前に、まずプログ ラムの振る舞いを観察し、問題を分析してください。これには Glance/gpm、HPjmeter、 ガベージ・コレクションのテクニックなどのツールを使用します。 ここで推奨されているチューニングのサイクルは、測定、分析、ボトルネックの特定、1 箇所の変更、再テストです。このドキュメントでは、HP のツールを使用した初心者のアプ ローチとして、 ► ► ► ► メモリ管理 スレッドの振る舞い システムと Java の設定 プログラム・リソースの消費 を解説してきました。これらの項目は Java アプリケーションのパフォーマンス・チューニン グに大きく貢献します。 4 参考文献 Sauers, R. and Weygant, P., HP-UX Performance Tuning, HP Press 45 (付録 A) ツールを用いた簡単なチューニング例 ここでは HPjmeter,HPjtune を用いた SPECjbb2000 のテスト実行のパフォーマンス問題 の解決の過程を簡単な例で示します。 (SEPCjbb2000 は Java サーバ アプリケーションのベンチマークとして広く利用されてい ます。SPECjbb2000 に関する詳細な仕様や、公式ベンチマーク結果については以下の ウェブサイトを参照してください。 http://www.spec.org/osg/jbb2000/) パフォーマンス 問題例 4-ウェイマシン上で 8 ウェアハウス(クライアントスレッド数に対応)での SPECjbb2000 の 実行結果のグラフを以下に示します。 14000 12000 Score 10000 8000 6000 4000 2000 0 1 2 3 4 5 6 7 8 Warehouse 4 ウェアハウスまでスコアは順調に増加していますが、5 ウェアハウス以上になると急激 にスコアは低下し、8 ウェアハウスではピーク時から約 40 パーセントものスコア低下が みられます。以下では、この問題について Hpjmter, HPjtune を使って解析を行います。 HPjmeter を使った 解析 SPECjbb2000 実行時の Java オプションに –Xeprof:file=jbb_before.eprof オプションを付 けプロファイルデータを jbb_before.eprof という名前のファイルに出力したとします。 1. HPjmeter を起動します。 (例:%java -mx128m HPjmeter) 2.メニューから[File]->[Open]を選択し、ダイアログからプロファイルデータがあるディレ クトリに移動し、jbb_before.eprof を選択しロードします。 46 3.表示されたデータを確認します。 4.メニュ-[Scope]->[Process]を選択したあと、メニュー[Metric] ->[Threads Histgram]を選択します。 Threads Histogram として以下が情報が簡潔に表示されます。 -プログラムの実行中の各スレッドの生存期間 -スレッドの時間がどのように使われたか 5. Thread-0(ウェアハウス 1)をダブルクリックし、パイチャートを表示させ [Detach]ボタンを押して切り離します。 47 6. 同様に、Thread-30 を(ウェアハウス 8)をダブルクリックし、 [Detach]ボタンをおして切り離します。 7. 2 つのパイチャートを比較すると以下のことがいえます。 -Thread-0 の CPU 時間が 62.4%を占めているのに対し、Thread-30 の CPU 時間が 21.0%だけである。 -Thread-0 はGarbage Colloction 時間はわずかだが、Thread-30 は 33.7%も占めて いる。 -Thrad-0,Thread-30 のどちらも Lock Contention 時間がない。(パフォ ーマンスの低下の原因は Lock Contention ではないと推 測可能) 8. 問題の原因は GC 関連の可能性が高いと推測されるので、次にヒープの状態 を詳しくみるために HPjtune を使って解析を行います 48 HPjtune を使った 解析 SPECjbb2000 実行時の java オプションに –Xverbosegc:file=jbb_before.epro を付けプロファイルデータを jbb_before.vgc という名 前のファイルに出力したとします。 1. HPjtune を起動します。 (例:%java -jar HPjtune.jar) 2. メニューから[File]->[Open]を選択し、ダイアログからプロファイルデータがあるディレ クトリに移動し、jbb_before.vgc を選択しロードします。 3.Summary パネルにデータが表示されるのでチェックします。 一番下ののステータスバーは最も重要な GC に使われた時間を表しています。左から 全 GC に使われた時間の割合(バー表示) フル GC に使われた時間の割合(バー表示) フル GC と全 GC の比較(パイチャート表示) このサマリパネルから以下のことがい得ます。 -15.55%もの時間が GC に使われている。(“Overall Statics” の”Percentage of time in GC”の欄) -ヒープの利用率は合計で 99.859%にもなる。(“Heap Capacity” の”Utilization”ラインの”Total”欄 49 4. Heap usage パネルを選択すると、表示内容から以下のことがわかります。 -ヒープの利用が単調増加し、ヒープが一杯状態に向っている。 -実行中に起こっている GC のタイプを見ると、前半は、Scavenge GC と System.gc だが、後半で Old 領域が一杯になったことが原因で Full GC が起 きている。 5.Duration パネルを選択すると、表示内容から以下のことがわかります。 -後半のある点から、GC 時間が急に増加している。 -Full GC にかかる時間は Scavenge GC にかかる時間に比べかなり長い。 50 6.Cumlative パネルを選択すると、表示内容から以下のことがわかります。 -作業量をあらわす Object Allocation が GC に時間がかかりはじめると、その 傾きが減少している。 結論 ここまでの解析の結果、パフォーマンスの劣化は GC に関連して起こって いるので、問題の原因は、ヒープサイズが十分でないのが原因と推測でき る。 51 チューニング 作業 これまでの HPjmeter,HPjtune での解析結果より、ヒープサイズの拡張を行います。この とき HPjtune の simulation 機能と実際のテストを繰り返し、ヒープサイズを適当な大きさ まで拡張します。 シミュレーション設定画面 今回は、初期ヒープサイズ 256M から 512M に拡張することにより、HPmeter,HPjtune で 以下の変化が観測されました。 Hpjtune での確認 --15.2%もの時間が GC に使われている時間が 15.2%から 4.44%に減少。 (“Overall Statics”の”Percentage of time in GC”欄) -ヒープの利用率の合計が 99.9%から 59.4%に減少(“Heap Capacity” の”Utilization”ラインの”Total”欄) 52 HPjmeter での確認 -Thread-30 の CPU 時間が 21.0%から 31.9%に増加 -Thread-30 の GC 時間が 33.7%から 5%に減少 チューニング 結果 以下のグラフに示すように、HPjmeter,HPjtune での解析をもとに、チューニング(ヒープサ イズを 256Mb から 512Mb に拡張)を行ったことによりパフォーマンスが上がっています。 (8 warehouse では約 60%のスコアの上昇) 16000 14000 Score 12000 10000 256 Mb heap 8000 512 Mb heap 6000 4000 2000 0 1 2 3 4 5 6 7 8 Warehouse チューニングによるパフォーマンス改善結果 53 (付録 B) HP-UX HotSpot JVM の Permanent 領域について HotSpot には Permanent 領域というヒープ領域があります。ここにはクラスの内部表現が 展開されます。具体的には、クラス、メソッド、フィールドなどのメタデータです。 一般に多くのアプリケーションにとって、Permanent 領域のデフォルトのサイズは十分な 大きさですが、アプリケーションによっては非常に多くのクラスをロードするものもあり、 Permanent 領域が足りなくなることがあります。このような場合には、Permanent 領域の サイズを変更する必要があります。Permanent 領域を多く消費する典型的な例は、JSP や servlet などです。 オプションによる サイズの指定 Java1.3.1.x ではこの Permanent 領域のサイズは、-XX:PermSize と -XX:MaxPermSize の 2 つで指定することができます。単位は、バイトです。 $ java –XX:PermSize=32m –XX:MaxPermSize=64m TestProg オプション名 -XX:PermSize -XX:MaxPermSize 意味 デフォルト値 Permanent Space の初期値 1MB Permanent Space の最大値 64MB 表 1. Permanent Space のサイズを指定するオプション Java1.2.2 では、デフォルトで初期値が 1MB、最大値が 32MB です。-XX:permSize や -XX:MaxPermSize でサイズを指定する場合、単位はキロバイトです。 Java1.3.1 では、これらの値は図 1 のように、-XheapInitialSizes を付けて java を実行す ると確認できます。 $ java -XheapInitialSizes -version NewRatio: 3 SurvivorRatio: 8 MaxTenuringThreshold: 32 Survivor size: 196608 Eden size: 1966080 New Size reserved: 22347776 initial: 2359296 Old Size reserved: 44761088 initial: 1441792 Perm Size reserved: 67108864 initial: 1048576 java version "1.3.1.01-release" Java 2 Runtime Environment, Standard Edition (build 1.3.1.01-release-010816-12:37) Java HotSpotTM Server VM (build 1.3.1 1.3.1.01-release-010816-13:34-PA_RISC2.0 PA2.0, mixed mode) 図 1. ヒープの初期値 54 Permanent 領域がいっぱいになると full GC が実行されます。GC 後も Permanent 領域の 割り当て要求を満たすことができない場合、VM は Permanent 領域を拡張します。 Permanent 領域は、初期値の大きさから最大値の大きさまで必要に応じて拡張されます。 クラスが GC の対象になるのは、そのクラスをロードしたクラスローダへの参照が無くな った場合(そのクラスローダが unreachable になった場合)のみです。 Permanent Space 不足によるエラー JSP や servlet を多用するアプリケーション(アプリケーションサーバなど)は、デフォルトの Permanent 領域サイズでは足りなくなることもあります。Permanent 領域を使い果たして しまった場合は、次のエラーが表示されます。このような場合には、-XX:MaxPermSize で十分な大きさを指定してください。 $ java ManyClassLoadingTest Permanent generation is full... increase MaxPermSize (current capacity is set to: 67108864 bytes ) Permanent generation is full... increase MaxPermSize (current capacity is set to: 67108864 bytes ) Exception in thread "main" Permanent generation is full... increase MaxPermSize (current capacity is set to: 67108864 bytes ) Permanent generation is full... increase MaxPermSize (current capacity is set to: 67108864 bytes ) 図 2. Permanent 領域不足時のエラーメッセージ クラスローダによる Permanent 領域への クラスのロード クラスファイルのロードは ClassLoader を使って行われます。ClassLoader には、デフォル トクラスローダとユーザ定義のクラスローダの 2 種類があります。 デフォルトクラスローダは、はじめから JVM に備わっているもので、システムクラスはこ のクラスローダでロードされます。アプリケーションのクラスも普通はこのクラスローダで ロードされます。 ユーザ定義クラスローダは、ClassLoader クラスを継承して作成するものです。これによ りユーザは作成したクラスローダを使ってクラスをロードすることが出来ます。 55 Permanent 領域と Garbage Collection Permanent 領域のクラスは以下の 2 つを満たす場合のみ、Full GC の対象になります。 ユーザ定義クラスローダによりロードされたクラスである ユーザ定義クラスローダへの参照が無くなっている(root set から unreachable になっている) つまり、デフォルトクラスローダでロードしたクラスや、ロードに使用したクラスローダへ の参照が残っているクラスの場合、Full GC が実行されてもその対象とはなりません。 また、Full GC が実行されるのは、以下の場合です。 ヒープの Old 領域が拡張されるときと最大値に達したとき 「Old 領域の空き領域 < New 領域内の全オブジェクトサイズ」の場合 (GC reason 5) Permanent 領域が拡張されるときと最大値に達したとき System.gc()が実行されたとき -Xnoclassgc オプションを付ければ、Full GC 実行時に Permanent 領域を GC の対象外に することができます。 以下は、GC 直前での Permanent 領域のサイズをプロットしたグラフで、-Xverbosegc の 出力をファイルに書出し、そのファイルを HPjtune で見たものです。この例では、 Permanent 領域が拡張されるたびに Full GC が行われています。また、Full GC が行わ れても Permanent 領域サイズが減少せずに 64MB すべてを使い果たしています。64MB のヒープを使い果たした後、図 2 のエラーを表示して JVM は終了します。Permanent 領 域を拡張するか、クラスローダの利用方法を見直せば(ユーザ定義クラスローダを使い、 かつ不要なクラスオブジェクトに対応するユーザ定義クラスローダへの参照をなくす)、 Permanent 領域が full になるのを防ぐことができます。 図 3. Permanent 領域の使用量 56 クラスのロード・アンロードは、-verbose:class オプションを付ければ見ることができます。 Permanent 領域の チューニング 動的にロードするクラスの数が有限であれば Permanent 領域のサイズもある値で飽和し ます。-Xverbosegc を指定して Java を起動し、すべてのアプリケーションのクラスをロード して、その時点の Permanent 領域のサイズを調べ、その値をもとに-XX:MaxPermSize で 指定するサイズを決めてください。 クラスのロードにデフォルトクラスローダを使っているのかユーザ定義のクラスローダを 使っているのか、またユーザ定義のクラスローダを使っていてもそのクラスローダへの 参照をどのように管理しているかによって Permanent 領域が Full GC の対象になるかど うかに違いが出てきます。これは、アプリケーションサーバがクラスローダをどのように 定義・利用しているかに依存します。 57 (付録 C) HP-UX 上での C ヒープと Java ヒープ HP-UX で Java を起動した場合、ヒープには C ヒープと Java ヒープの2種類があります。 C ヒープは、C/C++のプログラムで使用する領域で、JVM が直接この領域を使用します。 ここには Java オブジェクトは置かれません。C ヒープはデータセグメント内に存在し、 maxdsiz で指定したサイズまで拡張されます。 Java ヒープとは、Java プログラムのオブジェクトが置かれる領域で、-Xmn や-Xmx などで 指定できる New 領域と Old 領域、及び Permanent 領域です。これらの領域は GC の対 象になります。Java ヒープは Java VM が HP-UX の mmap() システムコールを使って割り 当てるメモリ領域で、C ヒープとは別の領域です。 C ヒープと Java ヒープのどちらが不足しても、Java の OutOfMemory Exception が起きま す。C ヒープがメモリ不足になるのと、Java ヒープがメモリ不足になるのとは意味が違い、 glance でチェックする領域も異なります。 HP-UX 11i 上の Java1.3.1 では JVM は 32bit モードで動作するので、プロセスのメモリ 空間全体は 4Gbyte です。 Java 1.4 以降では 64bit モードをサポートがされるのでメモリ 空間全体は理論上 16Tbyte と格段に増加し、ヒープに使用できるメモリサイズの制限も 飛躍的に緩和されます。 ただし、どちらの場合についても物理メモリとスワップスペースのサイズの制限を受けま す GlancePlus ではメモリ 消費量がどのように 見えるか GlancePlus で java プロセス(Java 1.3.1)のメモリ領域を見ると、下図のように見えます。 C ヒープはデータセグメント、Java ヒープは mmap() 領域にあることがわかります。Java 1.2.2 では、Java ヒープは 1 つの領域として表示されます。 Java アプリケーションのメモリ消費量(メモリ上のオブジェクトのサイズ)は、正確には -Xverbosegc の出力を hpjtune を使って見ることができます。 58 59 (付録 D) Java ヒープサイズの制限について(PA-RISC 版 SDK) HP-UX の 32 ビット プロセスにおける Large heap サポート HP-UX SDK 1.3 の java プロセス及び SDK1.4 の 32 ビットモード java プロセスのような HP-UX の 32 ビットプロセスでは 4GB のメモリ空間のアドレスを指定することができます が、HP-UX ではメモリ管理の効率を上げるために、この 4GB のメモリ空間を 4 つに分割 して各 1GB ごとに shared か private かを指定します。このしくみのもと、HP-UX のメモリ 空間は各リリースにおいて次のように変わってきています。 o 以前の HP-UX は最初の 1GB が TEXT(shared)、次の 1GB が DATA(private)、そのあと の 2GB が shared library 等(shared)と固定になっていました。つまりデータ領域に 1GB しかとれませんでした。 o 10.x からは EXEC_MAGIC を指定してコンパイルすることにより、最初の TEXT 領域の 1GB を private にできるようになり、最初の 2GB にデータを置けるようになりました。 EXEC_MAGIC・NORMAL_MAGIC・SHMEM_MAGIC はいわゆるマジック ナンバーで、 コンパイル時にコンパイルオプションで指定することにより異なる特性の実行形式を作 成できます。 o 11.0 にパッチをいくつかあてることにより、Large heap がサポートされるようになりまし た(11.0 ACE9911 以降ではこれらのパッチは不要です)。 PHKL_21039, PHKL_20222, PHKL_20223, PHKL_20224, PHKL_20225, PHKL_20226, PHKL_20227, PHKL_20228 PHKL_20174 これにより、3 番目の 1GB も private にできるようになり、全体で最初の 3GB にデータ を置けるようになりました。これは、chatr コマンドを使って「chatr +q3p 実行ファイル」 を 実行することにより実現されます。設定された情報は「chatr 実行ファイル」でみることが できます。 o 11i ではさらに 4 番目の 1GB も「chatr +q4p 実行ファイル」を 実行することで private にできるようになり、メモリ空間のほとんどに private データをおけるようになりました。 実 際には 0xc0000000 を syscall gate として空けておかなくては いけないなどいくつか制 限があります。 当然ながら、これには物理メモリ+スワップサイズの大きさが十分とられていることが前 提です。 Large heap と Java HP-UX の JVM には、java_q3p(3GB が private)と java_q4p(4GB すべてが private)と いう 2 つの実行ファイルがあり、指定されたヒープのサイズにより起動する JVM を切り替 えています。 60 HP-UX 11.0 では、1.3.1.05 までの Java は Large heap が使えず、最初の 2GB の領域 にしか Java heap などのデータを置けませんでしたが、1.3.1.06 からは 3GB までの領域 に Java heap を置くことができるようになりました。HP-UX 11i では、このような制限は無く、 ほぼ全領域にあたる 3.8GB のにヒープをとることができます。 (但し、3GB 以上の Java ヒープを利用する場合は、New 領域、または、Old 領域のどち らかがおよそ 850MB 以下でなくてはいけません。これは HP-UX の仮想アドレス空間管 理のセグメンテーションによる制限です。また、Java ヒープが割り当てられるプライベート 領域は、スレッドスタックや C ヒープも割り当てられるのでその兼ね合いによる制限も受 けます。) SDK 1.4 の 64 ビットモードを使用すれば、上記のようなデータサイズの制限は無くなり、 実質、物理メモリ+スワップサイズの制限が効いてくることになります。 リリース毎の Java ヒープサイズの詳しい情報についてはリリースノートを参照してくださ い。 61 (付録 E) Java スタックトレースによるリソース競合検出 Hotspot 1.3 での Java スタックトレース解析によるリソース競合の検出について説明しま す。 “2-6 リソースを大量に消費するメソッド・コール” に記述されているようにリソース競合 の検出は、通常 HPjmeter の利用によって効率よく行えます。しかし、Java サーバ・プログ ラムがクライアント・プログラムに対して反応しなくなった時などは、Hpjmtere でプロファ イリングデータをプログラム実行後に解析するよりも、正にその時点の状態の Java スタ ックトレースを解析してリソース競合状況を把握する方法がリソース競合検出に有効な 場合があります。 ここでは、Java スタックトレースのリソース競合状態時の特徴について述べます。 Java スタックトレースは java プロセスに SIGQUIT シグナルを送る(%kill –SIGQUIT <java process id>)か、もしくは、java プロセスがフォアグランド状態であれば<Ctrl>+\ (バックス ラッシュ) を実行することで、java プロセスを終了させることなく、その時点での各スレッド のスタックトレースを標準出力に出力することができます。 各スレッドスタックのヘッダは以下のようになっています。 “Thread2" prio=9 tid=0x1e8560 nid=10 lwp_id=13109 runnable [0x6fc16000..0x6fc16438] 前から順に、以下の意味です。 Java プログラムが付けたスレッド名 prio :Java プログラム内でこのスレッドに対し割り当てられた優先順位 tid :Hotspot C++オブジェクトへのポインタ nid :pthread の id lwp_id :glance 内で表される tid に対応 状態 [x..y] :このスレッドのスタック領域 ロックによる スレッドの同期 Java では共有リソースに複数のスレッドが同時にアクセスできないように オブジェクトに対してロックを設定することができます。(全てのオブジェクトには、対応す るロックが存在します。) 1 つスレッドだけが排他的に行うべきコードの範囲(クリティカルセクション)は以下のよう に実行されます。 まず、スレッドはクリティカルセクションの実行を行うまえに、その処理が実装され ているオブジェクトに対してロックを設定しようとします。 62 a. そのオブジェクトにロックが設定されていない場合 オブジェクトにロックを設定し(スタックトレース 内にロックに関する情報とし て "locked"が含まれる)、クリティカルセクションの実行を行い、実行終了後 にそのロック解除します。 b.そのオブジェクトに対し既に他のスレッドがロックを設定していた場合 ロックを設定したスレッドがその オブジェクトのロックを解除し、ロックを設 定しようとしているスレッドがそのオブジェクトのロックを 設定できるまで待 ちます。 (そのときスレッドの状態は "waiting for monitor entry"となってお り、スタックトレース 内で ロックに関する情報として"waiting to lock"が含ま れる。) ロックを設定出来たら、クリティカルセクションを実行し、実行後にそのロッ クを解除します。 また、スレッドがロックを設定した後、リティカルセクションを実行中に、なんらかの 原因で実行条件が成立していないために実行を続けることができないような場合 は、wait コールを行い実効条件が成立するまで(notify で通知されるまで)待機する こともあります。(そのときのスレッドの状態は "waiting on monitor"となっており、 スタックトレース 内にはロックに関する情報として"waiting for lock"が含まれま す。) スレッド状態 以下は、上記の”ロックによるスレッドの同期”の仕組みを踏まえた上で、リソース競合に よるパフォーマンスの劣化が起こっているかどうかを分析するときに注目すべきスレッド 状態(”runnable”状態、”waiting on monitor”状態, ”waiting for monitor entry”状態)の説 明です ①“runnable”状態 通常、スレッドが処理を行っているときは状態は”runnable”です。これはその時点で、 java コードを実行しているかネイティブメソッド内であることを示しています。 以下は I/O 待ちのソケットコールで I/O 待ち状態のスレッドですが、Java バーチャル・マ シンからみると実行(runnable)状態です。 "Thread-0" prio=9 tid=0x1bef58 nid=8 lwp_id=13105 runnable [0x6fc58000..0x6fc58438] at java.net.SocketInputStream.socketRead(Native Method) at java.net.SocketInputStream.read(SocketInputStream.java:90) at java.net.SocketInputStream.read(SocketInputStream.java:106) at SocketClose$SocketListener.run(SocketClose.java:18) at java.lang.Thread.run(Thread.java:479) ②”waiting on monitor”状態 waiting on monitor"状態のスタックトレースの例を以下に示します。スレッドダンプの出 力内に以下のロックに関する情報が含まれるのが特徴です。 waiting on locked : 63 "waiter 2" prio=9 tid=0xfe460 nid=10 lwp_id=16212 waiting on monitor [0x6fce7000..0x6fce7478] at java.lang.Object.wait(Native Method) - waiting on <0x68c15260> (a ObjectWaiterApp) at java.lang.Object.wait(Object.java:424) at WaiterThread.ComputeSome(ObjectWaiterApp.java:107) - locked <0x68c15260> (a ObjectWaiterApp) at WaiterThread.run(ObjectWaiterApp.java:158) "waiter 1" prio=9 tid=0xfdfc8 nid=9 lwp_id=16211 waiting on monitor [0x6fd08000..0x6fd08478] at java.lang.Object.wait(Native Method) - waiting on <0x68c15260> (a ObjectWaiterApp) at java.lang.Object.wait(Object.java:424) at WaiterThread.ComputeSome(ObjectWaiterApp.java:107) - locked <0x68c15260> (a ObjectWaiterApp) at WaiterThread.run(ObjectWaiterApp.java:158) これらのスレッド先に述べたように、オブジェクトに関するロックを獲得後、Object.wait() コールによって、一旦ロックを開放し、通知(notification)を待っている状態です。 一つのモニタに対して多くのスレッドが waiting の状態であることは必ずしも悪いことで はありません。それはアプリケーションの設計に依存します。つまり、設計によってはス レッド間の同期を synchronized method の代わりに wait()を使う場合があるからです。し かし、同期アルゴリズムの実装において誤用をしないように注意をする必要がありま す。 ③”waiting for monitor entry”状態 waiting for monitor entry"状態のスレッドが多く存在する場合は、望ましくない状態であ るといえます。スレッドダンプの出力内に以下のロックに関する情報が含まれます。 このような状態のスレッドが多数ある場合は、設計に問題があったり、割り当てたリソー スがスレッド数に対して少なすぎることを示しています。 "msg 10-985216529353" prio=9 tid=0x1bad30 nid=61 lwp_id=12190 waiting for monitor entry [0x67642000..0x67642478] at MsgThread.rest(ObjectWaiterApp.java:203) - waiting to lock <0x6bcc0978> (a java.lang.Class) at MsgThread.run(ObjectWaiterApp.java:222) "msg 0-985216529350" prio=9 tid=0x2a7980 nid=60 lwp_id=12189 waiting for monitor entry [0x67708000..0x67708478] at MsgThread.rest(ObjectWaiterApp.java:203) - waiting to lock <0x6bcc0978> (a java.lang.Class) at MsgThread.run(ObjectWaiterApp.java:222) "msg 10-985216529256" prio=9 tid=0x1b9b30 nid=58 lwp_id=12187 waiting for monitor entry [0x67684000..0x67684478] at MsgThread.rest(ObjectWaiterApp.java:203) - waiting to lock <0x6bcc0978> (a java.lang.Class) at MsgThread.run(ObjectWaiterApp.java:222) 64 デッドロック 複数のスレッドがお互いに依存したリソース競合を起こした結果、処理が進まなくなった 状態をデッドロックといいます。 以下の例は、2 つのスレッドがお互いに相手が保持しているロックに対して”waiting for monitor entry”状態であるためにデッドロックを起こしている例です。デッドロックはアプリ ケーションに問題があることを示しています。原因を追求して問題を解消することが必要 です。 "msg 2" prio=9 tid=0x155080 nid=31 lwp_id=16245 waiting for monitor entry [0x67852000..0x67852478] at MsgThread.rest(ObjectWaiterApp.java:203) - waiting to lock <0x6bcc0af0> (a java.lang.Class) at MsgThread.doMoreWork(ObjectWaiterApp.java:196) - locked <0x68c16450> (a java.lang.Object) at MsgThread.run(ObjectWaiterApp.java:224) "msg 4" prio=9 tid=0x1bd3c8 nid=29 lwp_id=16243 waiting for monitor entry [0x67894000..0x67894478] at MsgThread.doMoreWork(ObjectWaiterApp.java:195) - waiting to lock <0x68c16450> (a java.lang.Object) at MsgThread.rest(ObjectWaiterApp.java:207) - locked <0x6bcc0af0> (a java.lang.Class) at MsgThread.run(ObjectWaiterApp.java:222) 65 (付録 F) OutOfMemoryError について Java アプリケーションを実行中に"OutOfMemoryError"が発生してプログラムが異常終 了してしまう場合があります。その原因は、エラーメッセージが示すようにメモリ領域(シ ステムメモリ、Java ヒープ)の不足が主なものですが、それ以外にメモリ領域不足が直接 の原因ではない場合もあり、問題の特定の際に混乱を引き起こす可能性もあります。こ こでは、"OutOfMemoryError"が発生する以下の主な原因それぞれについて、エラーメ ッセージ例、確認方法、対処方法について説明します。 - Java ヒープ(Old 領域)不足 - 仮想メモリ不足 - カーネルパラメータ値が小さすぎる ただし、エラーメッセージ 例についてはシステムの状況によっては異なるメッセージが発 せられる場合もあります。 Java ヒープ (Old 領域)不足 Java アプリケーションとして最も注意すべきのがこの Java ヒープ領域サイズです。シ ステムのメインメモリに余裕があるにもかかわらず、このエラーがでた場合は、Java ヒー プ不足の可能性が高いといえます。 Java ヒープ不足のケースも大きくわけて 2 種類あります。 1. アプリケーションの正常動作のために必要な領域が確保されていない 2. Java メモリリークやプログラムの設計上の問題により、不要なオブジェクトが 残ったままになってしまい結果的に Java ヒープが不足してしまう 基本的にはアプリケーションの実行に必要な Java ヒープの量の見積りを行ったり、実 際にアプリケーションを実行したときのメモリ利用量を測定を行うなどして、適切な Java ヒープの必要量の把握、及び、メモリリークの発見、修正を行い、適切な量の Java ヒー プの確保した上で、不足する事態におちいらないようにすることが必要です。 エラーメッセージ例: Exception in thread "main" java.lang.OutOfMemoryError <<no stack trace available>> 確認方法: 大きなヒープサイズを指定して(-XX:+AggresiveHeap オプションの利用が可能 な場合は指定するなどして)、エラーの発生がなくなれば、Java ヒープ不足の 可能性が高いといえます。 HPjtune を使い GC 後の Old 領域内のオブジェクトの量の変化をチェックし、 使 用量が Old 領域を越えている場合は Java ヒープ不足の可能性が高いといえ ます。(見積もり以上に Old 領域内のオブジェクトが増加している場合は Java メモリ・リテンションの疑いがあります。) 66 対処方法: Java ヒープ領域サイズ(-Xmx オプションなどで)を適切な量まで増やします プログラムが生成するオブジェクト量を減らせないか検討します。 メモリ・リテンションがあれば修正します(2-7 メモリ・リークの章参照) 仮想メモリ不足 仮想メモリ不足とはシステムが利用できるメモリが不足してしまった状態です。Java ヒー プの利用量が増加し、システム全体で必要なメモリ量が、仮想メモリ量(物理メモリ量+ス ワップスペース量)をこえた場合にこのエラーが発生します。 エラーメッセージ例: "Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread" 同時にカーネルが以下のようなエラーメッセージを発する場合もあります。 - exec(2): insufficient swap or memory available.` - cannot fork: no swap space 確認方法: 実行時に swapinfo(1M)や、システム解析ツール GlancePlus でスワップの利 用量(GlancePlus メインメニュー“Reports”→”Swap Space”)を観察し、swap が不 足していないかを観察します。 対処方法: メモリリークがあれば修正します(2-7 メモリ・リークの章参照) プログラムが必要なメモリ量を減らせないか検討します 物理メモリ、swap 領域を増設します 擬似スワップを利用していない場合は、利用を検討します(カーネルパラメータ SWAPMEM_ON) カーネル パラメータ値 が小さすぎる カーネルパラメータの設定による制限を受ける場合があります。この場合は カーネル パラメータの値を増加させることで制限を緩和することができます。 エラーメッセージ例: max_thread_proc の値が必要量より小さい場合 Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread maxdsize の値が必要量より小さい場合 Exception in thread "main" java.lang.OutOfMemoryError: requested XXXX bytes Possible causes: - not enough swap space left, or - kernel parameter MAXDSIZ is very small. 確認方法: 67 sam (1M)の[Kernel Configuration] →[Configurable Parameters]で各パラメータの 設定値を確認します GlancePlus をつかいそれぞれ以下の項目をチェック。 max_thread_proc について java プロセスのスレッド数が max_thread_proc 数に到達しそうかどう かをチェック(GlancePlus メインメニュー”Reaports” →“process list”から、 java プロセスを選択し、”Reports”→ “Process Thread List”を選択) maxdsize について java プロセスの VSS のサイズが maxdsize に到達しそうかどうかをチ ェック(GlancePlaus メインメニュー”Reaports” →“process list”から、 java プロセスを選択し、”Reports”→“Process Memory Regions”を選 択) 対処方法: sam(1M)の[kernel configuration]や kmtune(1m)等を使い、現在のシステムの 設定値と使用量を観察し、カーネルパラメータを適切な値に増やします。 (HP-UX 11i では max_thread_proc はダイナミック調整可能なパラメータになったためリブ ートが必要ありませんが、HP-UX 11.0 ではリブートが必要です。) 68 (付録 G) SDK 1.4 での新しいガベージコレクション(GC) SDK 1.4.1 から 2 種類のパラレル GC、および、1 種類のコンカレント GC 合計 3 種類の新しいガベージコレクション(GC)が利用できます。(サポート状況の詳細に ついては各 SDK リリースノートを参照してください) 以下に 3 種類の GC の JVM オプションを示します。 2 種類のパレレル GC のうちの 1 つ(-XX:+UseParNewGC)は、コンカレント GC (-XX:+UseConcMarkSweepGC)と組み合わせることができ、リアルタイムに近い処理や GC 停止時間が大きな問題である処理を行うアプリケーションに対して効果が期待でき ます。もう 1 つのパラレル GC(-XX:+UseParallelGC)は主にエンタープライズのような大規 模なシステムもしくはスループット重視のアプリケーション(J2EE 等)に効果的だといえま す。尚、パラレル GC の対象は New 領域で、コンカレント GC の対象は Old 領域です。 以上の説明をまとめたのが以下の表です。 GC 種類 GC 対象領域 JVM オプション -XX:+UseParNewGC パラレル GC New 領域 -XX:+UseParallelGC 主な効果 アプリケーション停 止時間減少 スループット向上 (特にギカバイトクラのヒ ープサイズ、多 CPU 時 に有効) アプリケーション停 止時間減少 (注) JDK1.4.1.01 では-XX:+UseParallelGC は XX:+UseConcMarkSweepGC と同時には 指定できません。 コンカレント GC OLD 領域 -XX:+UseConcMarkSweepGC 以下はパラレル GC とコンカレント GC についての簡単な説明です。 パラレル GC パレレル GC はこれまで New 領域の GC を 1 つのスレッドが行っていたのと違い、CPU と同数のスレッドが並列的に GC を行います。 アプリケーション スレッド アプリケーション停止時間 GC スレッド 69 コンカレント GC コンカレント GC はガベージコレクションの処理の一部をアプリケーションスレッドと同時 行います。その結果、アプリケーションスレッド全てが停止する時間を減少させることが できます。 アプリケーション スレッド アプリケーション停止時間 GC スレッド コンカレント GC 関連のオプションには以下のものがあります。 -XX:ParallelGCThreads=<n> パラレル GC を行うスレッド数を指定します。 (デフォルト値: CPU 数) -XX:CSMInitiatingOccupancyFraction=<n> GC を開始する場合の Old 領域のオブジェクトの割当の割合を指定します。 (デフォルト値:68) コンカレント GC が完了しない場合が多いときは、この値を小さくします。 (次章 の”HPjmeter サポート”の項目を参照) 70 (付録 H) HP-UX 11i Version 2(11.23) 、Java2 1.4 パフォーマンス関連情報 Java2 プラット フォーム 以下の表は、HP-UX のバージョンとプロセッサアーキテクチャ、Java2 バージョンの対応 を示したものです。 HP-UX バージョン プロセッサ アーキテクチャ PA-RISC サポート Java2 バージョン 1.2,1.3,1.4 HP-UX 11i Version 1 (11.11) HP-UX 11i Version 1.6 Intel Itanium 1.3 ,1.4 (11.22) HP-UX 11i Version 2 Intel Itanium 1.3,1.4 (11.23) 本章は、主に HP-UX 11i Version 2(11.23)、Java2 1.4 環境についての情報です。 パフォーマンス チューニングツール 本書で紹介した以下のパフォーマンスチューニングツール全てが Itanium アーキテクチ ャ(HP-UX 11i v1.6(11.22), 11i v2(11.23))でもサポートされています。 - HPjconfig (カーネルコンフィグレーションツール) HPjtune (ガベージコレクション解析ツール) HPjmeter (Java アプリケーションパフォーマンス解析ツール) Glance Plus HP-UX 11i v2(11.23)ではインストール時に、選択可能なソフトウェアバンドルとして Java Out of Box を選択でき、サーバサイド Java アプリケーションに適切なカーネルパラメータ 値を設定できます。 HPjconfig は HP-UX 11i v1(11.11)ではカーネルパラメータ値設定のために SAM(1M)用 のファイルを出力していましたが、HP-UX 11i v2(11.23)では設定用のシェルスクリプトを 生成(HPjcofnig [SUPER USER]パネル)します。生成されたシェルスクリプトを super user となって実行すると、設定したカーネルパラメータの変更が行われます。 HPjtune 対応については以下の “HPjtune サポート”の項目を参照してください。 カーネルコンフィグ レーション HP-UX 11i v1.6(11.22), 11i v2(11.23)ではパフォーマンスチューニングの際に必要な、 カーネルコンフィグレーションについて機能強化が行われました。 ダイナミック調整可能カーネル パラメータの追加 カーネル コンフィグレーション ツール(kcweb) 71 ダイナミック調整可能カーネルパラメータ ダイナミック調整可能カーネルパラメータはシステムをリブートすることなくその値を変更 でき、ダウンタイムの削減に役立ちます。HP-UX ではバージョンを重ねるごとにダイナミ ック調整可能なカーネルパラメータが追加されています。 HP-UX 11i v1(11.11) (PA-RISC) HP-UX 11i v1.6(11.22) (Itanium) このリリースでの追加 maxdsiz(*) maxdsiz_64bit maxssiz(*) maxssiz_64bit nkthread(*) nproc(*) max_thread_proc(*) ksi_allco_max shmmni secure_sid_scrpts max_acct_file_size HP-UX 11i v2(11.23) (Itanium) このリリースでの追加 shmmax dbc_max_pct maxuproc dbc_min_pct shmseg nfile(*) maxfiles_lim(*) nflocks maxtsiz msgmnb msgmax maxtsiz_64bit core_addshemem_read core_addreshmem_write scsi_max_qdepth semmsl (*)J2EE システムのチューニング時によく変更されるものです。 カーネルパラメータの変更はコマンドを利用したり、次節のカーネルコンフィグレーション ツール(kcweb)を利用することもできます。(詳細については「HP-UX システム/ワークグ ループの管理」 (http://www.docs.hp.com/ja/5187-2219/index.html)を参照して下さ い。) カーネル コンフィグレーション ツール(kcweb) kcweb は HP-UX 11i v1.6(11.22), 11i v2(11.23)に付属の非常に強力なカーネルコンフ ィグレーションツールです。カーネル全般のコンフィグレーションを web ベースの GUI を 使って直感的な操作で効率良く行うことができます。カーネルパラメータを変更する必要 性が生じた場合は、この kcweb を利用出来ます。 (HP-UX 11i v1(11.11)に関しては以下のサイトからベータ版を入手することができます。 http://software.hp.com/products/KCWEB/index.html) kcweb を利用するとパラメータの関連情報(パラメータの意味、ダイナミック調整の可否、 現在値、デフォルト値、他のパラメータとの関連、値の制限 等)も素早く知ることができ ます。 また、カーネルパラメータに関するリソースが実際にどれくらい消費されているかをモニ タする機能もあり、実際に現在のカーネルパラメータ値が適切であるかどうかを判断す る材料を得ることもできます。 72 kcweb のカーネルパラメータ調整画面 (max_thread_proc パラメータを選択) Java2 1.4 JVM パフォーマンス 関連強化機能 Java2 1.4 環境での JVM の主なパフォーマンスチューニングに関する強化機能について は以下のものがあります。これらの機能は HP-UX 11i v1(11.11) (PA-RISC), HP-UX 11i v1.6(11.22), 11i v2(11.23) (Itanium)のどちらのプラットフォーム上の JVM でもサポートさ れています。 64bit モード ガベージコレクション(GC)強化 GC プロファイリング強化 GC 監視オプション追加 以下、それぞれの項目についての説明です。 64bit モード Java2 1.4 ではデフォルトの 32bit モードに加えて、64bit モードがサポートされています (-d64 オプション)。これまでの 32bit モードの制限である最大ヒープサイズ PA-RISC:約 3800MB(HP-UX 11i v1(11.11)以降), Itanium:3500MB(HP-UX 11i v2(11.23)以降)より 大きな量のヒープを利用できます。64bit モードでは 32bit のプロセス空間サイズの制限 はなくなりますが、実際にはヒープサイズは物理メモリ、ディスクスペース、スワップスペ 73 ースなどによって制限されます。一方、ガベージコレクションによる性能低下の観点から もあまり大きなヒープサイズは現実的でない場合があります。 Java ヒープはデフォルトでは lazy swap 属性を使ってメモリ領域を確保するので、実行時 に実際にスワップが利用されるまで実際には領域を確保されません。したがって、実行 時に実際に利用できるスワップが足りない場合はプログラムが実行途中で強制終了させ られてしまう場合があります。メモリ、スワップのサイズを決めるときは注意が必要です。 ガベージコレクション(GC)強化 Java2 1.4 のガベージコレクション強化については”(付録 G) 新しいガベージコレクション (GC)”を参照してください。 GC プロファイリング強化 ガベージコレクション強化にともないプロファイリングデータも強化されました。これらに 変更については HPjtune でも対応済みです。 プロファイルヘッダ Java2 1.4 環境では、HPjtune 用のプロファイルデータ(-Xverbosegc オプションでプロファ イリング)には、ヘッダに新たに以下の情報が含まれます。これらの情報はチューニング の際のログとしても役立ちます。 - JVM バージョン、 - システム情報(メモリ量、スワップ量、CPU.数) - OS メモリ関連情報、 - ヒープパラメータ、 - ヒープ領域サイズ - GC 種類 -Xverbosegc のプロファイルデータのヘッダ例(一部) <GCH: vgc=2.1 > <GCH: vm="Java HotSpotTM Server VM mixed mode" > <GCH: vmrelease="1.4.1 1.4.1.05-030910-05:38-IA64N IA64" > <GCH: starttime="Fri Jan 23 10:45:53 JST 2004" > <GCH: hostname=userhost01 > <GCH: os=B.11.23 > <GCH: machine=ia64 > <GCH: physmem=4187284 > <GCH: mode=n > <GCH: ncpu=2 > …. <GCH: argXms=0 > <GCH: argXmx=0 > <GCH: optNewSize=2304 > <GCH: optMaxNewSize=4194240 > <GCH: optOldSize=16384 > <GCH: optMaxHeapSize=65536 > <GCH: optNewRatio=2 > <GCH: optSurvivorRatio=8 > <GCH: optMaxTenuringThreshold=31 > ……. <GCH: optUseParNewGC=0 > <GCH: optUseParallelGC=0 > 74 パラレル GC(-XX:+UseParallelGC)対応 -Xverbosegc や-Xloggc で収集されたデータはパラレル GC の場合も有効であり HPjmeter で解析可能です。 コンカレント GC(-XX:+UseConcMarkSweepGC)サポート -Xverbosegc( Java2 1.4.1.05, 1.4.2 以降)は、コンカレント GC もサポートしています。 HPjtune でも CMS(Concurrent Mark&Sweep)対応として以下のメトリクス等が追加されま した。 Stop_the_World GC Time プログラム実行中にガベージコレクションによって、アプリケーションが停止し た(stop_the_world)時間 Incomplete_CMS wasted time 完了しなかったコンカレント GC 中の stop_the_world 時間 (GC が完了していないので、この時間は浪費されたことになります。) CMS concurrent runnig time コンカレント GC がアプリケーションと同時に実行された時間 詳細については以下のサイトを参照してください。 http://www.hp.com/products1/unix/java/java2/hpjtune/index.html GC 監視オプション追加 HPjtune で利用する –Xverbosegc オプションの他も目的に応じて GC に関する情報を出 力することができます。(いずれも標準オプションではないので、将来的に使えなくなる 場合があるので注意が必要です) -Xloggc:filename GC 情報(一般的な情報をファイルへ出力) -XX:+PrintGCDetails GC 情報(詳細) -Xverbosegc GC 情報(詳細)を出力(HP-UX JVM のみ) -XX:+PrintTenuringDistribution オブジェクトの年齢情報 -XX:+TraceGen0Time New 世代領域の累積 GC 時間、GC 回数、平均 GC 時間 -XX:+TraceGen1Time Old 世代領域の累積 GC 時間、GC 回数、平均 GC 時間 -XX:+PrintGCTimeStamps GC のタイムスタンプ情報 -XX:+PrintHeapAtGC GC 前後の詳細なヒープ情報 75 変更履歴 Date Revision 2002.4 1.0 2002.10 2.0 2003.6 2.1 2004.2 3.0 アップデート内容 初版リリース 以下を追加 1-3 HP Java パフォーマンスツール 2-8 HPjmeter を使ったメモリ・リテンションの解析 (付録 A) ツールを用いた簡単なチューニング例 (付録 B) HP-UX HotSpot JVM の Permanent 領域について (付録 C) HP-UX 上での C ヒープと Java ヒープ 以下をアップデート 2-9 その他の JVM オプション 以下を追加 (付録 D) Java ヒープサイズの制限について (付録 E) Java スタックトレースによるリソース競合検出 (付録 F) OutOfMemoryError について (付録 G) 新しいガベージコレクション(GC) 以下をアップデート (付録 G) 新しいガベージコレクション(GC) 以下を追加 (付録 H) HP-UX 11i Version 2(11.23) Java2 1.4 パフォーマン ス関連情報 お問い合わせはカスタマー・インフォメーションセンターへ 03-5304-6660 月∼金 9:00∼19:00 土 10:00∼18:00(日、祝祭日、年末年始および5/1を除く) HP-UX製品に関する情報は http://www.hp.com/jp/hpux 記載されている会社名および商品名は、各社の商標または登録商標です。 記載事項は2004年2月現在のものです。 本書に記載された内容は、予告なく変更されることがあります。 ©Copyright 2004 Hewlett-Packard Development Company, L.P. 日本ヒューレット・パッカード株式会社 〒140-8641 東京都品川区東品川2-2-24 天王洲セントラルタワー JHS04031-01