Spigot(Bukkit)のJava起動オプションはそこまでチューニングする必要はないというお話

タイトルの通りです。VPSでSpigotを動かしたときにJava起動オプションを色々アレコレやった時のお話です。

# 起動オプションは公式Wikiのとおりで大抵OK
起動用のスクリプトは[Spigot Installation](https://www.spigotmc.org/wiki/spigot-installation/)に書かれています。
例にLinux版を以下に引用します。

```shell:start.sh
#!/bin/sh
java -Xms1G -Xmx1G -XX:+UseConcMarkSweepGC -jar spigot.jar
```

メモリープールの初期割り当てサイズ1GBが-Xms1G、最大割り当てサイズ1GBが-Xmx1Gに相当します。
ただこれは**メモリーが十分ある環境下に限られます**。

# メモリーが限られる環境下での実行
VPSなどでメモリー1GBクラスの箱をデプロイしてしまうとメモリーはシステムに数百MBほど吸われてます。このままでは上記の起動スクリプトでSpigotが動かない可能性があるのでWindowsで言うところの仮想メモリ、スワップメモリーは当然割り当てておきます。（※この節はLinux系のOSを前提にしています。）
最初は**4GB程あれば十分で、必要なら後で足せば良いだけです**。VPSだとディスク容量も限られるのでこの辺が無難かと思います。
~~ただ[BuildTools](https://hub.spigotmc.org/jenkins/job/BuildTools/)は`OutOfMemoryError`と出て動かない可能性があります。当方の環境がそうだったので素直にローカルでビルドしました。~~

Mavenにメモリーの割り当て+αしてあげることで解決しました。

```shell:build.sh
#!/bin/sh
export MAVEN_OPTS="-Xmx2G -XX:+UseG1GC"
java -Xmx2G -XX:+UseG1GC -jar BuildTools.jar
```

## ただ効率が落ちる
メモリープールの初期割り当てサイズ1GBのままではスワップメモリーを使う事になります。上記にも書きましたがシステムに数百MBほど使われて残りは700MBとかその辺ぐらいになってくるかと思います。
仮に残り700MBとして初期割り当てサイズ1GBであれば300MBほどスワップメモリを使うことになります。

スワップメモリーを使うと言うことはディスクの読み書きが発生してきます。
ここで書き込みなどで遅れが生じるとラグや巻き戻りなどの原因になってきます。

可能なら初期割り当てのオプション-Xms*は削除してしまうか、半分の-Xms512Mと初期割り当てを512MB程で様子を見るなどしてみましょう。

ちなみに当方の環境ではディスクの読み書きが押さえられた。
![vps_4_1.png](https://qiita-image-store.s3.amazonaws.com/0/70146/abefca7a-7b13-565d-22e6-c855ac7755d8.png)

-Xms*は切（消して）ったり、減らしたりしても大丈夫なのかですが、問題はありません。
**最初にメモリーを確保しておけば、使うとき（プログラム内でインスタンスなどを生成したとき）早いよね**ってだけで、必須オプションではありません。メモリーが十分にあるなら切る理由は無いですよね。

## ガベージコレクションはCMSのままで良いのか
ガベージコレクションをザックリ説明すると使ったメモリーのうち、不要となってきた部分を解放してメモリーの空きをつくるものです。メモリーの掃除って所です。

参考：

-  [5分で分かるガベージコレクションの仕組み | geechs magazine](https://geechs-magazine.com/tag/tech/20160229) - ガベージコレクションについてはこの記事がとても分かりやすく書いてあります
-  [Java VMのガーベジコレクションの整理](https://qiita.com/sipadan2003/items/a7acf7f0375d818b5045) 
-  [OutOfMemoryError の調べ方 - Qiita](https://qiita.com/opengl-8080/items/64152ee9965441f7667b)

で、**CMS**は**コンカレント・マーク・スイープ**の略称でガベージコレクションのアルゴリズムの一つです。
これもザックリ言うと掃除の方法（仕方）です。CMSという掃除のやり方でメモリーを解放していきます。

ちなみに上記で書いた起動用のスクリプトはこのCMSを使うようオプションには-XX:+UseConcMarkSweepGCとあります。

特にCMSで問題は無いように見えたのですがうちの環境では少しFull GCが発生してたのが気になりました。`OutOfMemoryError`は発生してませんがこれが膨らむようなら可能性はあります。

実は**CMSが将来的に使え無くなる可能性**があります。
詳しくは続きを読んでみてね。

### CMSに余計な味付けをしては成らない

インクリメンタルモード(-XX:+CMSIncrementalMode)は使わないようにしよう。

> CMSコレクタは、コンカレント・フェーズがインクリメンタルに行われるモードで使用できます。すでに説明したように、コンカレント・フェーズ中はガベージ・コレクタ・スレッドが1つ以上のプロセッサを使用しています。インクリメンタル・モードは、コンカレント・フェーズを**定期的に停止**してプロセッサをアプリケーションに返すことにより、長時間にわたるコンカレント・フェーズの影響を軽減することを目的としています。

うちではこの”定期的に停止”が原因でラグなど発生してた可能性があった。
というより

> インクリメンタル・モードはJava SE 8で非推奨となり、今後のメジャー・リリースでは削除される可能性があります。

とあるのでインクリメンタルモードは使わない。

出典：https://docs.oracle.com/javase/jp/8/docs/technotes/guides/vm/gctuning/cms.html

## 黒船来航G1GC

ガベージファースト ガベージコレクタ、略してG1GC。
これもガベージコレクションのアルゴリズムの一つです。ただしこれは新しいヤツ！

Java7 (java7u4)から正式に実装されたものでJava9からは標準でコレが使われます。そして**CMSは非推奨**となりました。

> ガベージファースト(G1)ガベージ・コレクタは、CMSのほとんどの使用を置き換えることを目的としています。

置き換えって事で将来的にCMSを削除してくる可能性はあるのかな？

出典：

- [Java SE 7 Update 4 Release Notes](http://www.oracle.com/technetwork/java/javase/7u4-relnotes-1575007.html)
- [Java Platform, Standard Edition Oracle JDK 9の新機能, リリース9](https://docs.oracle.com/javase/jp/9/whatsnew/toc.htm#GUID-B6CD8C25-FD93-4CAA-9286-19A39CC0F26A)

### 設定が小難しいのでデフォルトでも良いが

参考：

- [ガベージファースト・ガベージ・コレクタのチューニング](https://docs.oracle.com/javase/jp/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html)
- [JVMチューニング: G1GCの使いどころとCMS GCからのマイグレート](https://x1.inkenkun.com/archives/5506)

> G1 GCは適応型ガベージ・コレクタであり、デフォルト値を変更せずにそのまま使用して効果的に動作できます。

「効果的に動作」とのことでまずはデフォルト、何も付けず様子を見てあげて下さい。

# まとめ：「最適化」「高速化」を謳う起動スクリプトは使ってはいけない
メモリープールの割り当てさえ十分出来ていれば他はJava VMが自動で何とかしてくれます。えらい！

> JVMでは、ガベージ・コレクタ、ヒープ・サイズおよびランタイム・コンパイラのデフォルト選択がプラットフォームごとに異なります。これらの選択は、コマンド行でのチューニングをあまり必要とせずに、各種アプリケーションのニーズに対応します。さらに、動作ベースのチューニングでは、アプリケーションの特定の動作に合せてヒープ・サイズを動的に調整します。

出典：[エルゴノミクス](https://docs.oracle.com/javase/jp/8/docs/technotes/guides/vm/gctuning/ergonomics.html#ergonomics)

マシンのスペックを越えて処理速度が上がるわけではありません。むしろ理解してないで変なJava起動オプションを足したことで非効率な状態になり、ラグや巻き戻りなどの原因になるよという事を伝える記事です。

またCMSとG1GC、どちらを使えば良いかですがこれも環境によります。
上記でも書きましたがCMSはJava 9で非推奨となったのでG1GCにシフトしてみるのもいいかもしれません。
ただ低スペック環境ではG1GCだと少し厳しい感じでもあります（CMSよりG1GCのほうが少しCPU使用率が高まる為）。理由は次の参考記事を読もう！

参考：

- [モーストリ・コンカレント・コレクタ](https://docs.oracle.com/javase/jp/8/docs/technotes/guides/vm/gctuning/concurrent.html#mostly_concurrent)
- [Javaガベージコレクション - ソフトウェアエンジニアリング - Torutk](http://www.torutk.com/projects/swe/wiki/Javaガベージコレクション)


G1GCを使う場合は以下のオプションを使います。Java 9以降ではただのおまじないになります。

```shell:start.sh
#!/bin/sh
java -Xms1G -Xmx1G -XX:+UseG1GC -jar spigot.jar
```

G1GCの特徴として「[一時停止時間目標](https://docs.oracle.com/javase/jp/8/docs/technotes/guides/vm/gctuning/g1_gc.html#pause_time_goal)」というものがあり、
デフォルトで200ミリ秒とかなり低い方ですが当方の環境では50ミリ秒に変えてラグを小さく設定する事でラグを極小に出来ないかと考えました。

```shell:start.sh
#!/bin/sh
java -Xms1G -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=50 -jar spigot.jar
```

チューニングするとしたらこの程度までに抑えた方が良いかもしれません。若干プラシーボ効果的な感じはします。ただ小さすぎても良くは無いので問題があるようなら増やすようにしてみましょう。
現状当方の環境ではこれでFull GCは発生してません。

----

色々ザックリなので間違ってるところがあればツッコミを歓迎します。
それ以外の「うちではどうやれば良いのか」云々のであれば無視します。ご了承下さい。