作成日:2003.06.26
更新日:2003.07.02
JDK のディレクトリを $JDK (%JDK%)、 jvmstat のディレクトリを $JVMSTAT (%JVMSTAT) とするなら以下のように実行パスを通す。
jvmstat/ ./bat/ ... Windows 用の実行バッチファイル ./bin/ ... Linux / Solaris の実行シェルスクリプト ./classes/ ... jvmstat のクラスファイル ./docs/ ... ドキュメント ./etc/ ./policies/
rem Windows set PATH=%JDK%\bin;%JVMSTAT\bat;%PATH # C Shell set path=$JDK/bin:%JVMSTAT/bin:$path
> java [-server or -client] [オプション] -XX:+UsePerfData [クラス名]
jvmps UNIX の ps コマンドのように -XX:+UsePerfDataを指定して起動した JavaVM の一覧を得ることができる。
オプションは以下の形式:オプションなしで jvmps と実行すると ローカルマシン上で動いている JavaVM の一覧が手に入る。
jvmps [ options ] [ host id ]
先頭の数字は Virtual Machine ID (VMID)と呼ばれる JavaVM の識別子で、 続く文字列は起動に使われた jar ファイル名またはクラス名が入る。 VMID は実際はプロセス ID だ。 ただし、 Linux のようにスレッド毎にプロセス ID が振られる実装でも、 JavaVM 毎に 1 つのプロセス ID が代表となり VMID となる。
% jvmps
18027 /opt/j2sdk1.4.1/demo/jfc/Java2D/Java2Demo.jar
18032 /usr/local/jvmstat/classes/jvmps.jar
オプションとしては -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 と 同様に指定して、 コマンドラインから以下のように入力し起動する。
追加のオプションとして監視を行う時間間隔を interval で指定できる。 100ms、10s のように ms(ミリ秒)・s(秒)の ポストフィックス文字を付けて指定。 ポストフィックス文字がない場合には ms が採用される。
% visualgc vmid [interval]
interval 無指定時のデフォルトの時間間隔は 0.5 秒 (500ms or 0.5s)。
詳しい見方を下に公開。
perfagent (後で書くよ)
この手法は 世代別 GC (Generational GC) と呼ばれる。Hotspot VM の世代別 GC は、 新世代のみを対象とする (無印) GC と、 全世代を対象とする Full GC の 2 つを使用する。 これは -verbose:gc を付けて実行した場合の 出力によって識別できる。
いろいろなプログラムの挙動を解析した結果、 プログラムデータには生成されてから消滅するまでの 寿命 に長短があり、 「寿命の短いオブジェクトほど量が多い」 という 統計的性質があることが分かった (*1)。 世代別 GC はこの性質を利用する GC 手法である。
(*1)世代別 GC は、 若い世代のみを対象とした高速だが洩れがある GC と、 古い世代 または すべての世代を対象とした 完全だが低速な GC を組み合わせた GC の方式である。
この統計的性質は正確ではなく、 もっと別のアプローチがいいと考える GC の一派もいる。
少なくとも、 オブジェクトが「死亡」する確率のピークは、 生成された直後ではなく 生成されてから「ちょっと経った」ところに出る。
新世代のみを対象とした(ノーマルの) GC (以後、新世代 GC) が行われる時には、 旧世代のオブジェクトは 生きていると仮定される。 旧世代に移動させられたオブジェクトは、 すでに GC を何度かくぐり抜けているので、 今回の GC でも生き延びる確率が高いからである。 GC の回収対象を絞ったことによって、 新世代 GC はヒープ全体を GC するより高速に実行できる。
ただし旧世代にあるオブジェクトも少しづつ死んでいくので、 「死んでしまった」のに「生きている」とみなされる旧世代オブジェクトが序々に増えてゆく。 そのため新世代 GC の効率は序々に低下してゆく。 旧世代中にごみとなるオブジェクトが閾値を越えて増えたと判断されたときに、 旧世代のみ または 全世代を対象とした GC が必要となる。 この場合は、 新世代のみの GC よりも大きな時間コストが掛かるのが普通だが、 一端 全世代 GC が走ってヒープ中のごみオブジェクトがきれいに取り除かれると、 次回からの新世代 GC が効率良く(短時間で済むように)なる。
オリジナルのコピーGC 方式は、 ヒープ空間を同じ大きさの 2 つの空間 (Survivor-0、Surivivor-1)に分割し、 毎回片側だけを使う GC 方式である。コピーGC 方式は 生き残るオブジェクトの割り合いが少ない場合には非常に効率がよい。 逆に生き残るオブジェクトの量が多いと (生き残るオブジェクトはメモリコピーされるので)効率が悪くなる。 Hotspot VM のように世代別 GC と併用すると、 長寿命な(生き残る)オブジェクトは旧世代に移動させおき、 新世代内のみにコピー 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 方式では生きているオブジェクトを探す操作が非常に簡単。
- スレッドのスタックから参照されている Survivor-0 のオブジェクトは生きている。 生きているオブジェクトは Survivor-1 にコピーする。 元の Survivor-0 のオブジェクトには「もうコピー済み」とう印を付けておく。
- Survivor-1 に積まれた「コピーされた」オブジェクトを底から順番に検査し、 「コピーされた」オブジェクトから参照されている Survivor-0 内のオブジェクトがないかをチェックする。 あれば、それは生きているオブジェクトなので Survivor-1 の一番上にコピーしておく。
- 2. で検査対象のオブジェクトがだんだん上にゆくと、 新しくコピーされたオブジェクトが対象となる。 そのため、 Survivor-1 の最後のオブジェクトを検査し終わった時が、 すべての生きているオブジェクトを探し出しことになる。