WASチューニングの巻（ヒープ）

[前回](https://qiita.com/ch7821/items/50ee9f667016ac9e45af)からの続きで、今回はヒープ容量を最適化してみます。

# 前提
[前回](https://qiita.com/ch7821/items/50ee9f667016ac9e45af)の記事と同じ環境を使用します。

# ゴールとアプローチ
+ スループット最大化を目指しつつ、一方でGC実行時間を最小化する

1. 最適なGCポリシーを選択する
2. 最適なヒープ容量を探る

# GCポリシーの選択
## GCアルゴリズム
GCのアルゴリズムには以下の４つがあり、JVMベンダにより多少の違い（方言）やラージオブジェクトの扱いの違いはあるものの、GCポリシーは基本的にこれらのアルゴリズムの組み合わせです。

<dl>
<dt>マーク・スウィープGC</dt>
<dd>マークフェーズで「いきている」オブジェクトをマークし、スウィープフェーズでマークされていないオブジェクトを削除（スウィープ）する。</dd>
<dt>コンパクション<dt>
<dd>マーク・スウィープすると、メモリの断片化が発生する。断片化したメモリ領域を再編成（オブジェクトをヒープ内に整列）する。</dd>
<dt>コピーGC</dt>
<dd>ヒープをFROM領域とTO領域に分けて管理する。アプリケーションがオブジェクトを生成するとFROM領域からメモリを確保する。FROMの空きが少なくなったらコピーGCで「いきている」オブジェクトをでTO領域に整列して移す。移し終わったらFROMとTO領域を反転する。</dd>
<dt>世代別GC</dt>
<dd>ヒープをNEW（Nursey）とOLD（Tenured）に分ける。アプリケーションがオブジェクトを生成するとNEW領域からメモリを確保する。NEW領域がいっぱいになったらスウィープしつつ一定回数以上「いきている」オブジェクトをOLDへ移す。</dd>
</dl>

## WASのGCポリシー
<dl>
<dt>Optimize for Throughput(optthruput)</dt>
<dd>マークとスウィープを毎回のGCで実行し、解放領域が少ない場合はコンパクションも実行する。</dd>
<dt>Optimize for Pause Time(optavgpause)</dt>
<dd>基本はマーク・スウィープ・コンパクションの組み合わせだが、マークを頻繁にやっておく（コンカレントフェーズ）ことでGCの実行時間（＝アプリケーションの停止時間）を短くする。CPUに余裕がある場合に有利。</dd>
<dt>Generation Concurrent(gencon)</dt>
<dd>デフォルト設定。ヒープはNurseyとTenured領域に分かれる。Nursey領域はさらにAllocate（FROM）とSurvivor（TO）領域に分けられ、AllocateがいっぱいになるとコピーGC（Scavenger Collection）が行われる。Tenured領域自体はOptimize for Pause Timeの方式でコンカレントフェーズにマーク、領域がいっぱいになったらスウィープ、解放されなかったらコンパクションとなる。</dd>
<dt>Balanced(balanced)</dt>
<dd>４GB以上のヒープ容量があり、Scavenger Collectionに時間がかかるようならこの方式にする。ヒープが少ないと逆効果（空き容量に余裕がある前提の構造になっている）。さらにCPUコア数が多い環境で有利。領域をブロック（最大ヒープサイズ/1024の2の倍数）に区切ってAllocate、コピー等のGCを適宜で行う。</dd>
</dl>
（）内は`-Xgcpolicy`JVM汎用引数に指定する値

というわけなので、今回はスループット優先、停止時間優先、世代数の３つでスループットがどう変わるか確認するとします。サイズは最大、初期値とも256 MBとします。
※WASの開発者版は合計2 GBメモリ内のライセンス制約あり

# verboseGCログのモニタリング
まずは冗長GCログを有効化してファイル出力させます。IBM JVMではXML形式で出力し、[GCMV](https://marketplace.eclipse.org/content/ibm-monitoring-and-diagnostic-tools-garbage-collection-and-memory-visualizer-gcmv)という専用の可視化ツールをEclipseプラグインとして提供しています。素直にこれを使います。

# ヒープの健全性クライテリア
一般的には以下の項目を確認します。

+ 当たり前だがOutOfMemoryErrorにならないこと
+ かといってJVMで使用可能なメモリ容量を超えてページングさせてしまってもダメ（一般的には使用可能なメモリの80%ほどを指定）
+ 単位時間あたりのGC実行時間が13%以内
+ GC後の空き領域が６割以上（85%以上になるならもう少し小さくしても構わない）
+ GCの間隔が長すぎない（数分程度）
+ GCの実行時間が長すぎない（３秒以上は長い）

# 実測
負荷をかけないと意味がないので、同時アクセス数=60にして、若干待ちが発生する状況下で３０分間負荷をかけ、GCの様子を確認します。スループットはJMeterのレポートから取得します。
## スループット優先
<img width="1092" alt="スクリーンショット 2018-03-04 16.38.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/887cce6a-bdaa-2b8b-90af-ded82fc340c6.png">

## GC時間優先
<img width="1083" alt="スクリーンショット 2018-03-04 17.19.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/3ed8a02d-78d2-0b50-a6be-e0d9777571ff.png">

## 世代別
<img width="1083" alt="スクリーンショット 2018-03-04 17.57.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/66d5170f-ae07-7580-2eb9-5fc943f0f45e.png">

# 結果

## GCポリシー
GCポリシーごとに結果をまとめると以下のようになりました。

| GC policy | throughput | cpu usage | gc time | gc interval |
|:--|:--|:--|:--|:--|
|Throughput   |11.78 tr/sec   |79.01 %   |10.48 %   |470 ms|
|Pause   |11.41 tr/sec   |82.42 %   |10.27 %   |472 ms|
|Gencon   |11.94 tr/sec   |71.86 %   |2.24 %   |125 ms (New), 94452 ms (global)|

スループットの差はわずかですがGenconが最もよいです。さらに単位時間あたりのGC時間を見ると、2.24 %と圧倒的にGenconが有利となる結果でした。また、CPU使用率についても最も有利な結果となっています。この無意味なアプリケーションに関しては、Genconが最適なGCポリシーといってよいと考えられます。

## ヒープサイズ
GenconでのヒープサイズのGCMVによるレポート結果（抜粋）です。
<img width="304" alt="スクリーンショット 2018-03-04 19.11.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/fc044289-a992-aa1a-2151-e416abb7939c.png"><img width="1016" alt="スクリーンショット 2018-03-04 19.12.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/fab1eb25-ee2b-6116-c03d-ed3e369a2615.png">

GC後の平均ヒープ使用率は50 %ですから、もう少しサイズ拡張してもよさそうです。
というわけで、ヒープサイズを640 MBまであげて再実測してみます。

| GC policy | throughput | cpu usage | gc time | gc interval |
|:--|:--|:--|:--|:--|
|Gencon(256 MB)   |11.94 tr/sec   |71.86 %   |2.24 %   |125 ms (New), 94452 ms (global)|
|Gencon(640 MB)   |12.35 tr/sec   |72.19 %   |1.07 %   |311 ms (New), 3082 ms (global)|

スループットは約3 %向上しました。誤差というには顕著な違いです。CPU使用率はほぼ変わりませんが、単位時間あたりのGC時間（＝アプリケーションの処理がストップしていた時間）が1.07 %と、割合でいえば半減しています。もちろん、単位時間あたりのGC時間だけでなく、レスポンスタイムのも気にしなくてはいけません。例えば、一人のユーザであっても、３０秒待ってしまうとか、タイムアウトしてしまうとかがあるとお話になりませんので。JMeterのレポートを見る限りこの辺りもクリアしていました（むしろ最大レスポンスタイムも改善していました）。平均値に注目しすぎると忘れがちな、外れ値の持つ意味はしっかり吟味しなければいけません。
New領域のGC間隔が３倍程度まで伸びたものの、長すぎることはなさそうです。GC後の空きも平均で70 %でした。
そして、256 MBのときには１分半間隔だったグローバルGCも３秒間隔まで落ちています。頻度が少し高いのが気になりますが、停止時間のメトリクスのがより重要なので、やはりこのアプリケーションに関してはヒープサイズ640 MBのが256 MBのときよりもよいと判断して良さそうです。
これ以上ヒープサイズを増やしてもスループット向上は見込めないと想定されます。どこまでなら向上するのかさらにチューニングするか悩ましいところではありますが、、、

というわけで、GCMVを使ってヒープの最適化を試みてみました、というお話でした。

# 参考
## 参照サイト
+ [Java仮想マシン(JVM)のチューニング](https://docs.oracle.com/cd/E28613_01/web.1211/b65905/jvm_tuning.htm)
+ [WAS管理ガイド（パフォーマンス）](http://public.dhe.ibm.com/software/dw/jp/websphere/was/was85_adminguide/V85Guide_10_Tuning.pdf)

## GCMV結果イメージ（抜粋）
### Throughput
<img width="622" alt="スクリーンショット 2018-03-04 18.51.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/78b80357-6c3f-b29a-fb27-64867a751b6c.png">

## Pause Time
<img width="600" alt="スクリーンショット 2018-03-04 18.51.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/2661dfe5-5c7c-56fe-8e30-b6c8d234f551.png">

## Gencon(256 MB)
<img width="769" alt="スクリーンショット 2018-03-04 18.51.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/62f53430-00a9-f933-e2bf-673f99a54244.png">

## Gencon(640 MB)
![名称未設定.png](https://qiita-image-store.s3.amazonaws.com/0/232587/1549fb13-865f-5dbd-4a54-542fd151235a.png)