とりあえずおさえておきたいGlassfishのチューニングリスト

この記事は 「[Java EE Advent Calendar 2016](http://qiita.com/advent-calendar/2016/javaee)」 10日目の記事です。
 9日目（前日）: [@megascus](https://twitter.com/megascus)さん「[Java EE Webアプリケーションをモジュール化して組み合わせる](http://d.hatena.ne.jp/megascus/20161209/1481252793)」
11日目（明日）: [@yyYank](https://twitter.com/yy_yank)さん

# はじめに
Java EEを始めて1年半が経ちました。開発にあたってGlassfishのパフォーマンスチューニングについて調べる機会があったので、そのときに調べたことをまとめておきます。

Glassfish（やその他のアプリケーションサーバー）はデフォルトでは「開発に最適化」されています。自動デプロイ機能やダイナミックリローディング、JVMに関しては高速な**「稼働」**より高速な**「起動」**を優先するようになったりしています。
これらの機能は開発段階ではとても便利なのですが、プロダクション段階ではパフォーマンスのボトルネックとなることがあります。

より質の高いサービスを提供するためにも、また、プロダクションの環境に合わせた設定の最適化方法を知るためにも、Glassfishのチューニング方法を知っていおくことは重要です。

今回は、そんなGlassfishの基本的なパフォーマンスチューニングについて解説していきます。

**注意: 開発段階でチューニングをすることはおすすめしませんし意味がありません。開発のテンポが悪くなります。**

----

なお、環境に依存するパラメータについてはそれぞれ次のように定義しておきます。

|パス|意味|
|:---:|:---:|
|\<glassfish>|Glassfishがインストールされているパス|
|\<domain-name>|チューニング対象のドメイン名|

また、設定ファイルについて、設定を追加する場合は

```xml:追加
<!-- 追加の設定 -->
```

設定を変更する場合は

```xml:変更前
<!-- 変更前の設定 -->
```

```xml:変更後
<!-- 変更後の設定 -->
```

で表します。

# チューニングの種類
チューニングには大きく分けて3つあります。

1. JVMのチューニング
1. Glassfishのチューニング
1. プログラムのチューニング

**1.JVMのチューニング**は「そもそもGlassfishを動かしている人を早くしよう」という発想です。他のJavaプログラムに応用できます。
**2.Glassfishのチューニング**は文字通り、Glassfish自体の設定を最適化して高速化を図ります。
**3.プログラムのチューニング**はGlassfishに実行させるプログラム自体を最適化することです。よりメモリ効率のいいコードやテクニックなどを駆使して高速化を図ります。この記事では扱いません。

# JVMのチューニング
はじめにJVMのチューニングについて考えていきましょう。
設定後は対象ドメインを再起動してください。
Glassfish起動時のJVMの設定は`<glassfish>/domains/<domain-name>/config/domain.xml`ファイルに記述していきます。
ファイル内に

```xml:domain.xml
<java-config ...>
 <jvm-options>..</jvm-options>
 <jvm-options>..</jvm-options>
 <jvm-options>..</jvm-options>
</java-config>
```

という部分があると思います。ここがGlassfish起動時にJVMに渡すオプションになります。

## 1. serverモードで動作するように

```xml:変更前
<jvm-options>-client</jvm-options>
```

```xml:変更後
<jvm-options>-server</jvm-options>
```

serverモードとclientモードの違いについては以下を参考にしてください。
[参考 - Real differences between “java -server” and “java -client”?](http://stackoverflow.com/questions/198577/real-differences-between-java-server-and-java-client)

## 2. ガーベジコレクションのログを出力するように

```xml:追加
<jvm-options>-verbose:gc</jvm-options> <!-- GCのログの有効化 -->
<jvm-options>-XX:+PrintGCDetails</jvm-options> <!-- より詳細な出力 -->
<jvm-options>-Xloggc:/path/to/logfile.log</jvm-options> <!-- ファイルへ出力 -->
```

\<path-to-log>にログの出力先を各自設定してください。
メモリ問題が起きた場合や割当メモリが足りているのか調べたいときに、ガーベジコレクションに不審な挙動がないかログを確認してみてください。
[参考 - ガーベジ・コレクション：GC ( Garbage Collection ) についての簡単な説明と調査方法](http://www.whitemark.co.jp/tec/java/javagc.html)

より詳しいGCについての情報を知りたい人は以下も参考に。
[補足 - JavaのGCに関するオプションについてまとめてみた。](http://qiita.com/YN0314/items/5e5c57d52ac1379daf6d)

## 3. System#gc() を使用できないようにする
```xml:追加
<jvm-options>-XX：+DisableExplicitGC</jvm-options>
```
System#gc()を手動実行するデメリットは以下のURLを参考にしてください。
[参考 - WebSphereべからず集: 第5回 「System#gc()を実行している」](http://www.ibm.com/developerworks/jp/websphere/library/was/was_nglist/5.html)

## 4. ヒープ領域に割当てるメモリの変更

```xml:変更前
<jvm-options>-Xmx512m</jvm-options>
```

デフォルトでは512MBを割り当てる設定になっています。
どれだけメモリを割り当てるかは負荷テストやガーベジコレクションのログなどで最適化していくのですが、そこまでこだわらない場合は、「とりあえずここまでは割り当てられる」という数値にしておきましょう。特に問題なければ「2GB」としておきます。

```xml:変更後
<jvm-options>-Xms2048m</jvm-options>
<jvm-options>-Xmx2048m</jvm-options>
```

「-Xms」は初期値、「-Xmx」は最大値を表しますが、これは一致させておくのがベストプラクティス。
初期値と最大値が一致しないと不要な割当処理が発生します。特に問題なければ一致させておきましょう。
以下により詳しいメモリチューニングの参考URLを貼っておきます。
興味がある方は読んで適宜設定を追加してください。
[参考 - JVMのチューニング](http://d.hatena.ne.jp/ogin_s57/20120709/1341836704)

GCが頻繁に発生するためヒープ領域を拡張したが問題が解決しない、ということがあります。
これは動かしているアプリケーションのプログラムに問題がある可能性があるので注意。
詳しくは以下を参照
[補足 - 9. ヒープサイズの拡大でメモリ問題は解決する](https://www.infoq.com/jp/articles/9_Fallacies_Java_Performance)

---
JVMのチューニングは掘り下げるとキリがないのでこの辺で終わっておきます。
興味がある人は調べてみてください。

# Glassfishのチューニング
本丸、Glassfishのチューニングをしていきます。
設定の変更は管理コンソール（localhost:4848）から行えますが、処理をスクリプトとしてまとめておけるコマンドによる変更を推奨します。
スクリプトとして保存しておけばサーバーを立てるたびにポチポチ設定を変更する必要がなくなります。

以下、`asadmin`というコマンドを使用しますが、このコマンドは`<glassfish>/bin/asadmin`にあります。

## 1. 自動デプロイを無効化する

```
$ ./asadmin set server.admin-service.das-config.autodeploy-enabled=false`
```

便利な自動デプロイ機能ですが、プロダクション段階では不要なのでオフにしましょう

## 2. ダイナミックリローディングを無効化する

```
$ ./asadmin set server.admin-service.das-config.dynamic-reload-enabled=false
```

ダイナミックリローディングとはサーバーを再起動することなくアプリケーションの変更分を自動で反映してくれる機能です。
非常に便利ですが、これが有効だとGlassfishは変更分がないか一定の間隔でチェックし続けます。
オフにしてパフォーマンス向上につなげましょう。
デフォルトでは2秒置きにチェックを行います。オフにしたくないが、パフォーマンスは多少あげたい場合はこの間隔を長めに設定しましょう。

```text:間隔を１分に変更する場合
$ ./asadmin set server.admin-service.das-config.dynamic-reload-poll-interval-in-seconds=60
```

## 3. JSPをプリコンパイルする

```
$ ./asadmin set server.admin-service.das-config.autodeploy-jsp-precompilation-enabled=true
```

JSP(JavaServerPages)を事前にプリコンパイルすることでパフォーマンスを向上させます。

## 4. ログレベルを厳しくする
```
$ ./asadmin set-log-levels <logger-name>=WARNING
```

大量のログの書き出しはパフォーマンスの低下につながります。
基本的にプロダクション段階ではWARNINGレベル以上のログがあれば十分なので、問題なければロガーのログレベルをWARNINGとしておきましょう。
（`./asadmin list-log-levels`コマンドでロガーとそのログレベルが一覧できます）

**注意: 運用初期などシステムが安定しない内は多めにログを吐いておいたほうが無難です**

## 5. genStrAsCharArrayを有効にする
genStrAsCharArrayオプションは文字列（String）を文字（char）の配列として扱うようにしてくれるオプションです。charの配列として扱う方が省メモリでオーバーヘッドが少なくなります。
「小さな最適化（micro-optimization; マイクロオプティマイゼーション）」と揶揄されるこのチューニングですが、塵も積もれば……

`<glassfish>/domains/<domain-name>/default-web.xml`内のjspサーブレットに関する設定箇所に追記します。

```xml:追加
<servlet>
    <servlet-name>jsp</servlet-name>
    ...
    ...
    <!-- 追加部分 -->
    <init-param>
       <param-name>genStrAsCharArray</param-name>
       <param-value>true</param-value>
    </init-param>
    <!-- /追加部分 -->
</servlet>
```

# おわりに
初めて参加した前回の[Java EE Advent Calendar 2015](http://qiita.com/advent-calendar/2015/javaee)からもう１年経ったのか……と絶望しながら書き終えました。
OracleがJavaEEの開発終了を匂わすなど、今後が心配ですが、Java EEには逆風に負けずに頑張ってもらいたいです。