コンテナ環境でのJVMメモリサイズの設定

# はじめに

株式会社セゾン情報システムズの松本です。
HULFT Square（以降 [HSQ](https://www.hulft.com/service/hulft-square)） という iPaas の開発プロジェクトでインフラを主に担当しています。


この記事は、数年前にJava8/11を対象に調査しため内容を元に記載していますので、少し情報が古いかもしれません。

# 背景など

`HSQ`ではマイクロサービスアーキテクチャを採用し、Backendのサービスは主にJavaアプリケーションで構成しコンテナ化しています。
コンテナ上では一つのサービスを動作させるのが[基本原則](https://docs.docker.com/config/containers/multi-service_container/)です。
そのサービスは複数のプロセスで構成されることもありますが、`HSQ`の各Backendサービスはコンテナ上の一つのJVMプロセスで構成されます。
そのため、コンテナの利用可能なメモリをほぼ全てを一つのJVMプロセスに割り当て可能です。

Backendサービスの一部は、お客様の専用サービスとして、メモリサイズを指定して作成することが可能です。
その指定されたメモリサイズに応じて、JVMメモリサイズを自動的に設定できないか？という疑問から調査し、サービスに反映しています。


# 前提情報

## JVM メモリ構成(Java8以降)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3695684/16900fe9-75b9-67fd-8dbc-a63db39c2f3f.png)
以下で構成されています。
- Java Heap
- Nativeメモリ
    - Metaspace
    - C Heap/Thread Stack

下記はJava7/Java8の違いについて書かれており、Java7以前の知識をお持ちの方にはわかりやすくおすすめです。
http://equj65.net/tech/java8hotspot/


## 物理サーバでのヒープサイズ

[Garbage Collection Ergonomics](https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gc-ergonomics.html) 

> Smaller of 1/4th of the physical memory or 1GB. Before Java SE 5.0, the default maximum heap size was 64MB. You can override this default using the -Xmx command-line option.

デフォルトで物理メモリの1/4を割り当てる設定です。

## コンテナ環境でのヒープサイズ
関連するJVM オプションを表にまとめます。
[Java8](https://docs.oracle.com/javase/jp/8/docs/technotes/tools/unix/java.html)の情報を元に記載しています。

`UseContainerSupport`はJava 10に追加されたオプションです（[JDK-8146115](https://bugs.openjdk.java.net/browse/JDK-8146115)）。 またJava 8u191などにもバックポートされました。


| JVM Option | Default Value | Note |
|:--|:-:|:--|
|UseContainerSupport   |true   |JavaアプリケーションをLinuxコンテナで実行すると、JVMはUseContainerSupportオプションでコントロールグループのメモリ制限を自動的に検出します。そして、次のオプション、InitialRAMPercentage、MaxRAMPercentage、MinRAMPercentageでメモリを制御することができます。ご覧のように分数ではなくパーセンテージになったことで、より使いやすくなりました。   |
|InitialRAMPercentage   |1.5625   |-XX:InitialRAMPercentageは、Javaアプリケーションの初期ヒープサイズの計算に使用されます。例えば、-XX:InitialRAMPercentage=25を設定し、全体の物理メモリ（またはコンテナ・メモリ）が1GBの場合、Javaアプリケーションのヒープ・サイズは～250MB（つまり、1GBの25％）になります。「XX:InitialRAMPercentage」は、JVM引数「-Xms」が渡されていない場合にのみ、初期ヒープサイズの算出に使用されます。Xms JVM 引数が渡された場合、「-XX:InitialRAMPercentage」は JVM によって無視されます。   |
|MinRAMPercentage   |50.0   |物理サーバ（またはコンテナ）で利用可能なメモリ全体のサイズが（およそ）250MB未満の場合にのみ、Javaのヒープサイズの計算に使用されます。   |
|MaxRAMPercentage   |25.0   |250MB(approximately). 物理サーバ（またはコンテナ）で利用可能な全体のメモリサイズが約250MB以上の場合にのみ、Javaのヒープサイズの計算に使用されます。   |

## Metaspace サイズ
関連するJVM オプションを表にまとめます。
[Java8](https://docs.oracle.com/javase/jp/8/docs/technotes/tools/unix/java.html)の情報を元に記載しています。

| JVM Option | Default Value | Note |
|:--|:-:|:--|
|MetaspaceSize   |環境依存   |初めて超えたときにFullGCを発生させるしきい値となるメタスペースのサイズを指定します   |
|MaxMetaspaceSize   |unsigned long   |メタスペースサイズの上限   |
|UseCompressedOops   |true   |https://www.baeldung.com/jvm-compressed-oops Java 7以降では、最大ヒープサイズが32GB未満の場合、oop圧縮がデフォルトの動作となります。最大ヒープサイズが32GB以上になると、JVMは自動的にoop圧縮をオフにします。そのため、ヒープサイズが32GBを超える場合のメモリ使用量は、別の方法で管理する必要があります。   |
|CompressedClassSpaceSize   |1GB   |Compressed Class Spaceの最大値を指定します。   |

# JVM メモリサイズのパーセンテージによる設定
## パーセンテージで設定できるもの
上記に示したように、JVM ヒープサイズは設定可能です。
`HSQ`のBackendサービスで利用したオプションと設定値を示します。
| JVM Option | Note |
|:--|:--|
|-XX:+UseContainerSupport   |コンテナサポートを有効する。デフォルトで有効ですが、明示的に指定しています。   |
|-XX:InitialRAMPercentage=75.0   |コンテナに割当られているメモリの75%を割り当てる   |
|-XX:MaxRAMPercentage=75.0   |コンテナに割当られているメモリの75%を割り当て   |


## パーセンテージで設定できないもの
Native領域の Metaspaceのサイズは、パーセンテージで設定できません。
絶対値のみの指定です。
ロードされるクラスファイルのサイズから見積もり、ある程度余裕を持って設定します。
但し、エンハンスなどで、ロードされるクラスファイルが増えていくとMetaspaceのサイズが不足し「java.lang.OutOfMemoryError: Metaspace」が出るケースがあります。
継続的にモニタリングしていく必要があります。

https://stackoverflow.com/questions/36465192/guidelines-to-set-metaspacesize-java-8
https://itpfdoc.hitachi.co.jp/manuals/link/cosmi_v0970/03Y0460D/EY040164.HTM


# Metaspaceサイズの自動算出について
[JSR(Java Specification Requests)](https://www.jcp.org/en/jsr/overview)にMetaspaceサイズをパーセンテージで指定する要求はなさそうです。（存在していたらごめんなさい）
必要なサイズを見積もった上で設定すべき値のため、必要とするユースケースが少ないのでしょう。

しかし、`HSQ`では、コンテナに割り当てるメモリサイズからMetaspaceのサイズの算出・設定を行うという要件がありました。
JVMオプションがない以上、コンテナに割り当てるサイズから、ヒープに割り当てるサイズを差し引き、Metaspaceの割当て可能なサイズを算出する仕組みを構築しています。

機会がありましたら、この事例ついてもご紹介いたします。

# さいごに
記載した内容は数年前の情報を元にしているため、新しい情報にキャッチアップしましたら追記もしくは別記事を記載したいと思います。
関連情報の連携がありましたら嬉しいです。また、間違いのご指摘などありましたら非常に助かります。

最後まで目を通して頂きありがとうございました！