Javaのメモリ調査ことはじめ

# Javaのメモリを確認するとき
Javaのメモリ調査をする場合の流れはこんな感じか。

* [そもそもJavaのメモリ構成がどうなっているか把握](#Javaのメモリ構成)
* [Javaのpid(vmid)を確認](#Javaのpid(vmid)を確認)
* [jstatコマンドでメモリ使用量を出力](#jstatコマンドで確認)
* [jstatコマンドの見方を覚える](#jstatコマンド出力結果の見方)
* [jstatコマンド結果を読み解く](#で、結局何を見たら良いの？)

---
# Javaのメモリ構成

そもそもJavaのメモリ管理がどうなっているんだっけ？の概要。
Java7(の途中)まではPermanet領域があったけど、Java8以降はMetaspaceに変更になっている。ここではJava8以降採用されたMetaspaceについて記載。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218194/826e2704-c0bc-bcf1-a3bd-f086e34b1337.png)


## Javaのpid(vmid)を確認
jpsやjcmdでvmidを確認。一般ユーザだと自分のvmidのみ確認可能。rootだと全てのvmidが表示される。
今回はお試し用にTomcatをインストール＆起動して確認。

```shell
# jps 
3636 Jps
709 Bootstrap
```
より詳細な情報を知りたければ、

```shell
# jcmd
709 org.apache.catalina.startup.Bootstrap start
3624 jdk.jcmd/sun.tools.jcmd.JCmd
```

更に詳細な情報の場合

```shell
# 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とかやると１０行ごとにヘッダ表示する。

1秒間隔でTomcat(pidが709)のメモリ状態を確認する場合の例。1秒間隔なのでメモリの推移に変化なし。


```shell:-gcオプション
# 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
```


```shell:-gcutilオプション
# 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に指定して起動

```shell:setenv.sh
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起動後、どの程度ヒープが利用されたか確認

```shell
# 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領域の使用量を確認

[こちらで詳しく解説](https://qiita.com/N_G/items/2dab8694fdebce2de868)していますが、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`を指定する
* Permanent領域からMetaSpaceの変更の情報は[ここを見た](http://equj65.net/tech/java8hotspot/)
    * ヒープ領域の一部だったのが、Nativeメモリとして扱われる
    * 保存する情報が違う
        * Permanent領域：クラスやメソッドのメタ情報の他、static変数、定数を保持
        * Metaspace：クラス、メソッドの情報のみ。static変数や定数はJavaヒープ上