jvmstat - Sun Hostspot VM パフォーマンスモニタツール

作成日:2003.06.26
更新日:2003.07.02


6/11 からサンフランシスコで開かれた JavaOne カンファレンスで、 SUN が開発中の技術を COOL STUFF という Web ページ上で先行公開していくことが発表された。 COOL STUFF のページで紹介されているプログラムの中で、 最も注目なのはjvmstat。 これは Java ランタイムのパフォーマンス情報を リアルタイムにモニタリングできるモニタリングツール。 jvmstat に附属する VisaulGC(visualgc)コマンドは 動作中の JavaVM のヒープメモリと GC の挙動を監視することができる (ただし、ターゲットは SUN の J2SE 1.4.1 以降に含まれる JavaVM のみ)。
こういうツールは Java システムのチューニングを行う上で必要不可欠。 BEA の JRockit の場合 Management Console や IBM VM の同様の機構をモニタリング機構を持っていたが、 本家の SUN の JavaVM にはその機能が不足していた。 今回、ようやくモニタリングのための API と可視化ツールが使用可能になった。


インストール

  1. (必要なら)まず Java2 Platform Standard Edition の v1.4.1 を ダウンロードしてくる (JRE でもよいはず)。
  2. jvmstat のページに行き registration を行った後、 ファイル(jvmstat.zip)をダウンロード。
  3. ファイルを展開を以下のようなディレクトリができる。
    jvmstat/
       ./bat/        ... Windows 用の実行バッチファイル 
       ./bin/        ... Linux / Solaris の実行シェルスクリプト
       ./classes/    ... jvmstat のクラスファイル
       ./docs/       ... ドキュメント 
       ./etc/
       ./policies/
    JDK のディレクトリを $JDK (%JDK%)、 jvmstat のディレクトリを $JVMSTAT (%JVMSTAT) とするなら以下のように実行パスを通す。
    rem Windows
    set PATH=%JDK%\bin;%JVMSTAT\bat;%PATH
    
    # C Shell
    set path=$JDK/bin:%JVMSTAT/bin:$path
以上で準備は OK。

使い方

監視対象となる JavaVM

jvmstat 系コマンドによってモニタリングを行うためには、 まず監視の対象となる JavaVM を -XX:+UsePerfData オプション付きで起動する。
> java [-server or -client] [オプション] -XX:+UsePerfData [クラス名]

コマンド

JVMStat には 以下の 4 つのコマンドが用意されている。
jvmps
UNIX の ps コマンドのように -XX:+UsePerfDataを指定して起動した JavaVM の一覧を得ることができる。

オプションは以下の形式:
jvmps [ options ] [ host id ]
オプションなしで jvmps と実行すると ローカルマシン上で動いている JavaVM の一覧が手に入る。
% jvmps
18027 /opt/j2sdk1.4.1/demo/jfc/Java2D/Java2Demo.jar
18032 /usr/local/jvmstat/classes/jvmps.jar
先頭の数字は Virtual Machine ID (VMID)と呼ばれる JavaVM の識別子で、 続く文字列は起動に使われた jar ファイル名またはクラス名が入る。 VMID は実際はプロセス ID だ。 ただし、 Linux のようにスレッド毎にプロセス ID が振られる実装でも、 JavaVM 毎に 1 つのプロセス ID が代表となり VMID となる。

オプションとしては -q が取れるが、 このオプションを指定すると VMID 以外の情報をカットして表示する。

host id を指定することによって、 他のマシン上で動作している JavaVM を指定することも可能だ。 この場合、 監視対象となるホストでは後述する perfagent が 動作している必要がある。 ローカルホストの jvmps と、 リモートホストの perfagent が RMI によって 通信しあってモニタリングを行う。 指定方法は、 以下のように >ホスト名<:>ポート番号< を並べる。 ポート番号はデフォルトの 1099 の場合は省略可能。
% jvmps remote.domain.com:1099
jvmstat
jvmstat-XX:+UsePerfDataを指定して起動した JavaVM の内部情報をモニタリングするコマンド。
jvmstat -help (ヘルプ表示)
jvmstat -help (ヘルプ表示)
jvmstat -version (バージョン番号表示)
jvmstat -options (オプションリスト表示)
(後で書くよ)
visualgc
JavaVM のパフォーマンスモニタ情報を可視化して、 表示するグラフィカルなビュアー。
モニタリングしたい JavaVM の vmid を jvmstat と 同様に指定して、 コマンドラインから以下のように入力し起動する。
% visualgc vmid [interval]
追加のオプションとして監視を行う時間間隔を interval で指定できる。 100ms10s のように ms(ミリ秒)・s(秒)の ポストフィックス文字を付けて指定。 ポストフィックス文字がない場合には ms が採用される。
interval 無指定時のデフォルトの時間間隔は 0.5 秒 (500ms or 0.5s)。

詳しい見方を下に公開。
perfagent
(後で書くよ)

Hotspot VM のメモリ管理の基本


(読み飛ばしても大丈夫)

visualgc の表示を知る前に Hotspot VM のメモリ管理の基本的なポイントを掴む必要がある。
Java 言語は、言語の仕様として以下のようなメモリ管理を持つことが要求されている。 しかし、 ヒープ空間と GC をどのように実装するかは Java の処理系、つまり JavaVM に任されている。
SUN の Java2 SE 1.2 以前の JavaVM (Classic VM と呼ばれていた) は ヒープ空間を連続した単一のメモリ空間と見ていたが、 Java2 SE 1.3 以降に採用された Hotspot VM は ヒープ空間を 2 つのサブ空間に分割する。 このサブ空間を 世代(Generation) と呼び、 一方を 新世代 (New Generation)、 もう一方を 旧世代 (Old Generation) と名付けている。 Hotspot VM は最初 新世代にオブジェクトを割り付けて、 新世代の空間がいっぱいになった時に (ノーマルの) GC を発生させる。 この (ノーマルの) GC では、 新世代内の中のごみオブジェクトのみが回収の対象となる。 (ノーマルの) GC を何度か生き延びたオブジェクトは 長寿命なオブジェクトと判定されて、 何度目かの GC において 新世代から旧世代に移動される。
この手法は 世代別 GC (Generational GC) と呼ばれる。
いろいろなプログラムの挙動を解析した結果、 プログラムデータには生成されてから消滅するまでの 寿命 に長短があり、 「寿命の短いオブジェクトほど量が多い」 という 統計的性質があることが分かった (*1)。 世代別 GC はこの性質を利用する GC 手法である。
(*1)
この統計的性質は正確ではなく、 もっと別のアプローチがいいと考える GC の一派もいる。
少なくとも、 オブジェクトが「死亡」する確率のピークは、 生成された直後ではなく 生成されてから「ちょっと経った」ところに出る。
世代別 GC は、 若い世代のみを対象とした高速だが洩れがある GC と、 古い世代 または すべての世代を対象とした 完全だが低速な GC を組み合わせた GC の方式である。

新世代のみを対象とした(ノーマルの) GC (以後、新世代 GC) が行われる時には、 旧世代のオブジェクトは 生きていると仮定される。 旧世代に移動させられたオブジェクトは、 すでに GC を何度かくぐり抜けているので、 今回の GC でも生き延びる確率が高いからである。 GC の回収対象を絞ったことによって、 新世代 GC はヒープ全体を GC するより高速に実行できる。

ただし旧世代にあるオブジェクトも少しづつ死んでいくので、 「死んでしまった」のに「生きている」とみなされる旧世代オブジェクトが序々に増えてゆく。 そのため新世代 GC の効率は序々に低下してゆく。 旧世代中にごみとなるオブジェクトが閾値を越えて増えたと判断されたときに、 旧世代のみ または 全世代を対象とした GC が必要となる。 この場合は、 新世代のみの GC よりも大きな時間コストが掛かるのが普通だが、 一端 全世代 GC が走ってヒープ中のごみオブジェクトがきれいに取り除かれると、 次回からの新世代 GC が効率良く(短時間で済むように)なる。
Hotspot VM の世代別 GC は、 新世代のみを対象とする (無印) GC と、 全世代を対象とする Full GC の 2 つを使用する。 これは -verbose:gc を付けて実行した場合の 出力によって識別できる。


さらに Hotspot VM は 、 (無印の新世代のみを対象とした) GC を高速化するために、 新世代の空間をさらに 3 つに分割し、 コピーGC方式(Copying GC) を導入している。
オリジナルのコピーGC 方式は、 ヒープ空間を同じ大きさの 2 つの空間 (Survivor-0Surivivor-1)に分割し、 毎回片側だけを使う GC 方式である。
まず、 Survivor-0 にオブジェクトを割り付けてゆき Survivor-0 がいっぱいになると GC を発生させる。 GC は Survivor-0 中の生きているオブジェクトを、 Survivor-1 にどんどんコピーしてゆく。 GC が終わると生きているオブジェクトは全部 Survivor-1 に移り、 Survivor-0 は空になる。 そこで、 Survivor-0 と Survivor-1 の立場を逆転させ GC は完了する。 GC 後は、 Survivor-1 が使用され、 Survivor-0 はリザーブ領域となる。

コピー GC 方式では生きているオブジェクトを探す操作が非常に簡単。
  1. スレッドのスタックから参照されている Survivor-0 のオブジェクトは生きている。 生きているオブジェクトは Survivor-1 にコピーする。 元の Survivor-0 のオブジェクトには「もうコピー済み」とう印を付けておく。
  2. Survivor-1 に積まれた「コピーされた」オブジェクトを底から順番に検査し、 「コピーされた」オブジェクトから参照されている Survivor-0 内のオブジェクトがないかをチェックする。 あれば、それは生きているオブジェクトなので Survivor-1 の一番上にコピーしておく。
  3. 2. で検査対象のオブジェクトがだんだん上にゆくと、 新しくコピーされたオブジェクトが対象となる。 そのため、 Survivor-1 の最後のオブジェクトを検査し終わった時が、 すべての生きているオブジェクトを探し出しことになる。
コピーGC 方式は 生き残るオブジェクトの割り合いが少ない場合には非常に効率がよい。 逆に生き残るオブジェクトの量が多いと (生き残るオブジェクトはメモリコピーされるので)効率が悪くなる。 Hotspot VM のように世代別 GC と併用すると、 長寿命な(生き残る)オブジェクトは旧世代に移動させおき、 新世代内のみにコピー GC を適用することができ非常に高速である。

しかしオリジナルのコピー GC 方式は空間を 2 つに分割してしまうため メモリ効率がよくない。 例えば、 1回の GC で新世代内のオブジェクトの 1/10 しか生き残らないとすると、 使われない方の Survivor 空間の 9/10 は不要だということになる。
そこで Hotspot VM では 2枚の Survivor 空間の前に Eden と呼ばれる空間を置く。 もし、 オブジェクトの生存率が 1/10 なら Eden と 2 つの Survivor 空間のサイズ比は 9 : 1 : 1 に 設定できる。
最初、 すべてのオブジェクトは Eden 空間に割り付けられるが、 GC が起きた場合 Eden 空間中の生き延びたオブジェクトを どちらかの Survivor (Survivor-0 と仮定)にコピーする。 Eden と Survivor-0 空間は 9 : 1 のサイズ比があるが、 Eden 中のオブジェクトの 9/10 は死亡して回収されるので 生き延びたオブジェクトは Survivor-0 空間に納まることになる。
次回の GC では Eden と Survivor-0 の生き延びたオブジェクトを Survivor-1 にコピーすることになる。 これも 1/10 しか生き残らないという仮定が正しければ Survivor-1 に納まりきる。

この 3 つの空間を利用するコピー GC 方式で、 Hotspot VM は新世代のメモリ使用効率を落とさずに高速な GC を実現している。 実際には Eden : Survivor-0 : Survivor-1 の比は プラットフォームによって異なるが 8 : 1 : 1 〜 12 : 1 : 1 に 設定されている。

参考:
Tuning Garbage Collection with the 1.4.2 JavaTM Virtual Machine

VisualGC コマンドの表示画面

visualgc を起動すると 3 つの Window が表示される。 それぞれの Window の表示と意味は以下の通り。
Visual GC Window
Visual GC Window Visual GC Window の上側を除くほとんど全部は JavaVM の使うヒープメモリをあらわしている。 ヒープ空間は EdenSurvivor0 (S0)Survivor1 (S1)Old に 分けられている。 あと左側に薄くある領域がPermである。
色が塗られた部分はメモリが使用されたことを意味している。

最初の 4 つの領域は Hotspot VM の GC と関係がある。
Hotspot VM は以下のような動作をするからである。
  1. Eden 領域は JavaVM 中のオブジェクトが 最初に割り当てられる領域 (そのため Eden 領域の色が塗られた部分は少しづつ大きくなる)。
    Eden 領域がいっぱいになったところで GC が発生する。
    GC が起こると Eden 領域内のうち生きているオブジェクトが S0 領域に移動させられる。 移動が終わった後は Eden 領域は空になり また割り付けが可能になる。
  2. 次に Eden 領域がいっぱいになって GC が起こった時には、 Eden と S0 (前回の GC によってオブジェクトが移動させられている)内の 生きているオブジェクトを S1 領域に移動する。
    Eden と S1 領域が空になる。
  3. そのまた次の GC が起こった時には、 S0 と S1 の立場が逆転して 2. を繰り返す。
  4. S0 と S1 の間を行ったり来たりする オブジェクトは「長寿命なオブジェクト」なので、 Old 領域に移動させられる。
  5. Old 領域が下の領域から移動してきたオブジェクトで いっぱいになったら、 Full GC と呼ばれる 全体的な GCを起こしてごみオブジェクトを回収する。
1. 〜 5. のような動作が繰り返されているのが、 Visual GC Window から観測できる。

Perm 領域は他の領域とは少し勝手が異なり、 Java クラスファイル内の情報等が保存される。
このデータは Eden 〜 Old に割り付けられる オブジェクトとはまざらない。
また、 Visual GC Window の上側には Application Information と呼ばれる枠があり、 モニタリング対象 JavaVM の生死、 起動してからの時間、 JavaVM に与えたオプション、 起動クラス以降の情報が表示される。

Graph Window
Graph Window Graph Window は、 時系列に沿った Eden、S0、S1、Old、Perm の使用済みメモリサイズの グラフが表示される。
また Just-in-time コンパイラが 1つのメソッドをコンパイルするのに要した時間と、 コンパイルしたメソッド数。
Class Loader が クラスを読み込むために停止していた時間と、 読み込んだクラス数。
GC が Java の実行を停止していた時間と、 GC の回数も同時に表示される。

Survivor Age Histogram Window
Survivor Age Histogram Window は やや説明が難しい。
Hotspot VM のオブジェクトは生成された時点を 0 歳として、 GC をくぐり抜ける度に 年齢(age) が増加していく。 これがオブジェクトの寿命を測る目安になっている。

Hotspot VM の場合、 Eden 領域に割り当てられた時が 0 歳、 最初の GC によって Survivor 領域に 移動させられた時が 1 歳となる。 以降 2 つの Survivor 領域の間で 行ったり来たりするうちに年齢が増加して行く。
Survivor Age Histogram は Survivor 空間中に残っているオブジェクトの年齢分布を、 各年齢のオブジェクトが占めているメモリ量であらわしている (0 歳はイレギュラーな理由により いきなり Survivor 領域に割り当てられたオブジェクトの分)。

Window 上部の Parameters には、 Tenuring ThresholdMax Tenuring ThresholdDesired Survivor SizeCurrent Survivor Size の 4 つの数値が並ぶ。


Survivor Age Histogram は細かい最適化を行うためには必要だが、 普通はあまり気にする必要はない。

Survivor Age Histogram Window

そのほか

(後で書くよ)

コメント

トラックバック   [Trackback URL: http://www.nminoru.jp/cgi-bin/tb.cgi/java__jvmstat__index]
コメントを書き込む

TOP    掲示板    戻る
Written by NAKAMURA Minoru, Twitter:@nminoru_jp