Javaのメモリを確認するとき
Javaのメモリ調査をする場合の流れはこんな感じか。
Javaのメモリ構成
そもそもJavaのメモリ管理がどうなっているんだっけ?の概要。
Java7(の途中)まではPermanet領域があったけど、Java8以降はMetaspaceに変更になっている。ここではJava8以降採用されたMetaspaceについて記載。
Javaのpid(vmid)を確認
jpsやjcmdでvmidを確認。一般ユーザだと自分のvmidのみ確認可能。rootだと全てのvmidが表示される。
今回はお試し用にTomcatをインストール&起動して確認。
# jps
3636 Jps
709 Bootstrap
より詳細な情報を知りたければ、
# jcmd
709 org.apache.catalina.startup.Bootstrap start
3624 jdk.jcmd/sun.tools.jcmd.JCmd
更に詳細な情報の場合
# jps -v
3648 Jps -Dapplication.home=/usr/lib/jvm/java-11-openjdk-11.0.9.11-3.el8_3.x86_64 -Xms8m -Djdk.module.main=jdk.jcmd
709 Bootstrap --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED -Djava.util.logging.config.file=/opt/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -Dcatalina.base=/opt/tomcat -Dcatalina.home=/opt/tomcat -Djava.io.tmpdir=/opt/tomcat/temp
該当するpidのメモリ使用量確認
jstatコマンドで確認
jstat <オプション> [-t] [-h ヘッダ表示行数] [pid 間隔(ms)]
- -t: タイムスタンプ表示
- -h: ヘッダを表示する行数。-h 10とかやると10行ごとにヘッダ表示する。
1秒間隔でTomcat(pidが709)のメモリ状態を確認する場合の例。1秒間隔なのでメモリの推移に変化なし。
# jstat -gc -h 3 709 1000
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT CGC CGCT GCT
512.0 512.0 0.0 52.0 4416.0 418.7 10944.0 9311.3 15104.0 14155.8 1792.0 1450.3 23 0.118 0 0.000 - - 0.118
512.0 512.0 0.0 52.0 4416.0 418.7 10944.0 9311.3 15104.0 14155.8 1792.0 1450.3 23 0.118 0 0.000 - - 0.118
512.0 512.0 0.0 52.0 4416.0 418.7 10944.0 9311.3 15104.0 14155.8 1792.0 1450.3 23 0.118 0 0.000 - - 0.118
^C
# jstat -gcutil 709 1000
S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT GCT
0.00 10.15 9.48 85.08 93.72 80.93 23 0.118 0 0.000 - - 0.118
0.00 10.15 9.48 85.08 93.72 80.93 23 0.118 0 0.000 - - 0.118
^C
jstatコマンド出力結果の見方
いろんな項目が出てくるので、主に利用する利用したことがあるオプションを整理すると、以下の通り。
項目 | 内容 |
---|---|
-gc | 実際に利用している数値をKBで表示する。 |
-gcutil | 数値の使用率(%)を表示する。 |
E,EC,EU
の数値を例に取ると、-gc
ではECが4416.0
、EUが418.0
、-gcutil
ではEが9.48
、となっている。
-gcオプションの場合、ECのEはE(den)、CはC(apacity)の意味なので、ECの数値はEden領域の実際のサイズ(Capacity)が4416KBあることを示している。またEUのUは使用量のU(sage)のことなので、EUの数値でEden領域が418KB利用されていることがわかる。
また-gcutilオプションは使用率なので、Eの意味は、Eden領域の使用率が9.48%であることを示している。この数値は以下でも確認できる。
EU(418.0KB) / EC(4416.0KB) * 100 = E(9.48%)
項目 | 内容 |
---|---|
S0/S1 | Survivor領域0もしくは領域1 |
E | Eden領域 |
O | Old領域 |
M | Metaspace領域 |
CCS | 圧縮されたクラス領域 |
- -gcオプションだと、上記項目のうしろに使用可能容量のC(apacity)、実際の使用量のU(sage)が付いた項目も表示される。
項目 | 内容 |
---|---|
YGC | YoungGC(マイナーGC) |
FGC | FullGC(メジャーGC) |
CGC | concurrent GC |
GC | GC(ガベージコレクション) |
- -gcオプションだとこの後ろに処理時間のT(ime)の付いた項目も表示される。
- CGCはコンカレントGC方式を指定しない場合、表示されないっぽい。
で、結局何を見たら良いの?
ヒープの使用量とOld領域の使用量をチェックするといい。
ヒープの使用量の確認
ヒープは「Eden + Survivor(from+to) + Old」となるので、以下でヒープの利用/確保領域がわかる。
- ヒープの確保領域:[S0C] + [S1C] + [EC] + [OC]
- ヒープの利用領域:[S0U] + [S1U] + [EU] + [OU]
- MetaSpaceは非ヒープ領域に移動になったので含まれないはず。
ヒープのメモリ計算例
Tomcatのヒープ最大/最小を512Mに指定して起動
CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.port=7900 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"
JAVA_OPTS="-Xms512M -Xmx512M -server"
Tomcat起動後、どの程度ヒープが利用されたか確認
# jstat -gc 4515 10000
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT CGC CGCT GCT
17472.0 17472.0 0.0 9064.7 139776.0 44358.5 349568.0 0.0 15488.0 14719.8 1920.0 1610.6 1 0.019 0 0.000 - - 0.019
ヒープの確保領域:[S0C:17472.0] + [S1C:17472.0] + [EC:139776.0] + [OC:349568.0] / 1024 = 512M
ヒープの利用領域:[S0U:0.0] + [S1U:9064.7] + [EU:44358.5] + [OU:0.0] / 1024 = 52.17M
となり、起動オプションで指定した512Mがヒープとして確保され、10%強のヒープが利用されていることがわかる。
Old領域の使用量を確認
こちらで詳しく解説していますが、Old領域の使用量(使用率)を確認することで、ある程度ヒープに問題があるか判断できます。
- OLD領域の利用サイズが大きく上下している場合
- FullGCが頻発しているということ
- 本来Old領域には利用中のオブイェクトが入るのでメモリ変動が少ないはずだが、短命オブジェクトが大量にあって、それらがOld領域から消された
- Young領域からOld領域に移る原因の一つとして、Young領域が小さすぎるため、短命オブジェクトがNew領域に存在できず、OLD領域に移動した可能が考えられる
- この結果Old領域にがすぐ一杯になり、FullGCが発生しOLD領域の短命オブジェクトが消去される
要は、Javaのメモリ管理では、一旦Old領域に移動したオブジェクトを消すタイミングは、FullGCのタイミングしかないので、短命オブジェクトはなるべくOld領域に移動させず、Young領域にいるうちにマイナーGCで消したほうが良い、ということ。
参考にさせていただいたURL
- https://qiita.com/opengl-8080/items/64152ee9965441f7667b
- https://qiita.com/kaikusakari/items/9b7c3d1fb524eb6aa348
- https://qiita.com/N_G/items/2dab8694fdebce2de868
おまけ
- GC方式が複数あるので、それにあったオプションを指定する。例えばJDK8から使えるG1GCを指定したのに、他のGC方式のオプションを指定しても無視される。
- 従来Permanent領域と呼んでいた領域はMetaSpaceに変わったので起動オプションも注意が必要。
- 例えばG1GCの場合、
-XX:MaxPermSize
でなく-XX:MaxMetaspaceSize
を指定する
- 例えばG1GCの場合、
- Permanent領域からMetaSpaceの変更の情報はここを見た
- ヒープ領域の一部だったのが、Nativeメモリとして扱われる
- 保存する情報が違う
- Permanent領域:クラスやメソッドのメタ情報の他、static変数、定数を保持
- Metaspace:クラス、メソッドの情報のみ。static変数や定数はJavaヒープ上