この記事は NTTドコモソリューションズ Advent Calendar 2025 13日目の記事です。
はじめに
NTT ドコモソリューションズの坂本翔平です。普段は OpenJDK に関する技術支援や調査に従事しています。また Java 関連の技術発表も行っています。
昨年の弊社 Advent Calendar では Metaspace の OutOfMemoryError を発生させてみた という記事を執筆させていただきました。
今回はその中で少し触れた -XX:CompressedClassSpaceSize という Java オプションの仕様を OpenJDK のソースコードから確認してみようという内容になります。
本記事の内容は記事公開時点(2025/12/13)の情報となります。今後のアップデート等により仕様変更される可能性がある点にはご注意ください。
気になった動作について
Metaspace とそれに含まれる領域として Compressed Class Space と呼ばれるメモリ領域があり、これらは以下の Java オプションで領域サイズをそれぞれ制御できます。
-
-XX:MaxMetaspaceSize: Metaspace 領域の最大サイズ(Capacity)を指定 -
-XX:CompressedClassSpaceSize: Compressed Class Space 領域の最大サイズ(Capacity) を指定
Metaspace については 前回の記事 でも説明していますのでご参照ください。
これらの Java オプションのデフォルト値は以下の通り、-XX:MaxMetaspaceSize は無制限、-XX:CompressedClassSpaceSize は 1GB となっています。
$ java -XX:+PrintFlagsFinal -version | grep -e MaxMetaspaceSize -e CompressedClassSpaceSize
size_t CompressedClassSpaceSize = 1073741824 {product} {default}
size_t MaxMetaspaceSize = 18446744073709551615 {product} {default}
ここで気になったのが -XX:MaxMetaspaceSize のみを 1GB 未満に指定し、-XX:CompressedClassSpaceSize を指定しない場合、各領域はどのように設定されるのかということです。
Compressed Class Space 領域は Metaspace 領域に含まれるものですので、MaxMetaspaceSize > CompressedClassSpaceSize とする必要があります。しかし -XX:CompressedClassSpaceSize は未指定の場合のデフォルト値は先に確認した通り 1GB ですので、上記の場合そのままですと MaxMetaspaceSize < CompressedClassSpaceSize となってしまいます。
以下の2つの Java バージョンで実際に -XX:MaxMetaspaceSize=500m のみ指定して動作確認してみました。
OS は Ubuntu 22.04 LTS、JDK は SDKMAN から取得したものを利用しています。
- Java 8
$ java -XX:MaxMetaspaceSize=500m -XX:+PrintFlagsFinal -version | grep -e MaxMetaspaceSize -e CompressedClassSpaceSize
uintx CompressedClassSpaceSize := 515899392 {product}
uintx MaxMetaspaceSize := 524288000 {product}
openjdk version "1.8.0_472"
OpenJDK Runtime Environment (Temurin)(build 1.8.0_472-b08)
OpenJDK 64-Bit Server VM (Temurin)(build 25.472-b08, mixed mode)
- Java 25
$ java -XX:MaxMetaspaceSize=500m -XX:+PrintFlagsFinal -version | grep -e MaxMetaspaceSize -e CompressedClassSpaceSize
size_t CompressedClassSpaceSize = 419430400 {product} {ergonomic}
size_t MaxMetaspaceSize = 524288000 {product} {command line}
openjdk version "25.0.1" 2025-10-21 LTS
OpenJDK Runtime Environment Temurin-25.0.1+8 (build 25.0.1+8-LTS)
OpenJDK 64-Bit Server VM Temurin-25.0.1+8 (build 25.0.1+8-LTS, mixed mode, sharing)
実行結果から両バージョンとも、指定した -XX:MaxMetaspaceSize=500m に対して MaxMetaspaceSize > CompressedClassSpaceSize となるように CompressedClassSpaceSize の値がデフォルト 1GB から調整されていることが分かります。
しかし調整された CompressedClassSpaceSize の値に差異があります。Java 8 の場合は MaxMetaspaceSize から 524288000 - 515899392 = 8388608 byte = 8MB 引いた値に調整されています。一方で Java 25 では MaxMetaspaceSize から 524288000 - 419430400 = 104857600 byte = 100MB 引いた値となっています。
この差異について、どのような実装となっているか OpenJDK ソースコードから確認してみました。
OpenJDK ソースコードから確認してみる
Java の実装である OpenJDK は OSS として GitHub にて公開 されています。このソースコードから CompressedClassSpaceSize がどのように調整されているか追っていきたいと思います。
Java 8
関連箇所の実装を抜粋したものは以下の通りです。
-
hotspot/src/share/vm/memory/metaspace.cpp(GitHub)
CompressedClassSpaceSize = align_size_down_bounded(CompressedClassSpaceSize, _reserve_alignment);
set_compressed_class_space_size(CompressedClassSpaceSize);
// Initial virtual space size will be calculated at global_initialize()
uintx min_metaspace_sz =
VIRTUALSPACEMULTIPLIER * InitialBootClassLoaderMetaspaceSize;
if (UseCompressedClassPointers) {
if ((min_metaspace_sz + CompressedClassSpaceSize) > MaxMetaspaceSize) {
if (min_metaspace_sz >= MaxMetaspaceSize) {
vm_exit_during_initialization("MaxMetaspaceSize is too small.");
} else {
FLAG_SET_ERGO(uintx, CompressedClassSpaceSize,
MaxMetaspaceSize - min_metaspace_sz);
}
}
} else if (min_metaspace_sz >= MaxMetaspaceSize) {
FLAG_SET_ERGO(uintx, InitialBootClassLoaderMetaspaceSize,
min_metaspace_sz);
}
上記の大まかな処理の流れは以下の通りです。
- CompressedClassSpaceSize の値をセット
-
(min_metaspace_sz + CompressedClassSpaceSize) > MaxMetaspaceSizeを評価- 真である場合は
min_metaspace_sz >= MaxMetaspaceSizeを評価- 真である場合は
MaxMetaspaceSizeが小さすぎるというエラーを出力し終了 - 偽である場合は
CompressedClassSpaceSizeにMaxMetaspaceSize - min_metaspace_szの計算値をセット
- 真である場合は
- 真である場合は
ここで出てくる min_metaspace_sz は以下のように定義されています。
uintx min_metaspace_sz =
VIRTUALSPACEMULTIPLIER * InitialBootClassLoaderMetaspaceSize;
VIRTUALSPACEMULTIPLIER は同じ metaspace.cpp ファイル内で定義されており、その値は 2 です。
-
hotspot/src/share/vm/memory/metaspace.cpp(GitHub)
#define VIRTUALSPACEMULTIPLIER 2
一方で InitialBootClassLoaderMetaspaceSize は以下で定義されており、LP64 環境では 4MB、それ以外では 2200KB がデフォルト値です。
-
hotspot/src/share/vm/runtime/globals.hpp(GitHub)
product(uintx, InitialBootClassLoaderMetaspaceSize, \
NOT_LP64(2200*K) LP64_ONLY(4*M), \
"Initial size of the boot class loader data metaspace") \
以上より動作確認時は min_metaspace_sz = 2 * 4MB = 8MB と算出されます。
MaxMetaspaceSize=500m で CompressedClassSpaceSize=1g ですので、前述した処理の流れから CompressedClassSpaceSize = MaxMetaspaceSize - min_metaspace_sz がセットされます。よって CompressedClassSpaceSize= 500 - 8 = 492MB となり、この値は動作確認したものと一致しています。
Java 25
関連箇所は以下になります。Java 8 と比較して実装が変更され、行数が増加しているため、部分的に抜粋しつつ説明します。
-
src/hotspot/share/memory/metaspace.cpp(GitHub)
まず以下の通り max_ccs_size を MaxMetaspaceSize の 80% の値に設定しています。
if (UseCompressedClassPointers) {
// Let Class Space not be larger than 80% of MaxMetaspaceSize. Note that is
// grossly over-dimensioned for most usage scenarios; typical ratio of
// class space : non class space usage is about 1:6. With many small classes,
// it can get as low as 1:2. It is not a big deal though since ccs is only
// reserved and will be committed on demand only.
const size_t max_ccs_size = 8 * (MaxMetaspaceSize / 10);
続いて adjusted_ccs_size として CompressedClassSpaceSize と max_ccs_size (と max_klass_range) から最も小さい値を選択して設定します。
// Sanity check.
const size_t max_klass_range = CompressedKlassPointers::max_klass_range_size();
assert(max_klass_range >= reserve_alignment(),
"Klass range (%zu) must cover at least a full root chunk (%zu)",
max_klass_range, reserve_alignment());
size_t adjusted_ccs_size = MIN3(CompressedClassSpaceSize, max_ccs_size, max_klass_range);
詳細は割愛しますが、max_klass_range は下限が 4GB になるように定義(GitHub)されています。ですので adjusted_ccs_size は 4GB を超えて設定されません。
なお -XX:CompressedClassSpaceSize は 4GB を超えて設定しようとするとエラーが出力されます。
$ java -XX:MaxMetaspaceSize=10g -XX:CompressedClassSpaceSize=5g -XX:+PrintFlagsFinal -version | grep -e MaxMetaspaceSize -e CompressedClassSpaceSize
size_t CompressedClassSpaceSize=5368709120 is outside the allowed range [ 1048576 ... 4294967296 ]
Improperly specified VM option 'CompressedClassSpaceSize=5g'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
その後、adjusted_ccs_size をルートチャンクのサイズ単位にアラインメントします。
// CCS must be aligned to root chunk size, and be at least the size of one
// root chunk.
adjusted_ccs_size = align_up(adjusted_ccs_size, reserve_alignment());
adjusted_ccs_size = MAX2(adjusted_ccs_size, reserve_alignment());
ルートチャンクとは、Metaspace を管理するための固定サイズのメモリ領域(チャンク)のうち最大サイズのものを指します。Java 25 の場合、ルートチャンクのサイズは 16MB で定義(GitHub)されています。
adjusted_ccs_size に CompressedClassSpaceSize の値が使用されている場合はそのまま使用します。上記までの処理の結果 adjusted_ccs_size と CompressedClassSpaceSize が異なる場合には adjusted_ccs_size の値を CompressedClassSpaceSize として使用するようにします。
// Note: re-adjusting may have us left with a CompressedClassSpaceSize
// larger than MaxMetaspaceSize for very small values of MaxMetaspaceSize.
// Lets just live with that, its not a big deal.
if (adjusted_ccs_size != CompressedClassSpaceSize) {
FLAG_SET_ERGO(CompressedClassSpaceSize, adjusted_ccs_size);
log_info(metaspace)("Setting CompressedClassSpaceSize to %zu.",
CompressedClassSpaceSize);
}
}
ここまでが処理の大まかな流れになります。動作確認の設定値では max_ccs_size = 8 * (500M / 10) = 400MB となり、CompressedClassSpaceSize は 1GB なのでadjusted_ccs_size が 400MB となり再設定されます。この値は動作確認したものと一致しています。
まとめ
OpenJDK ソースコードから確認した、MaxMetaspaceSize < CompressedClassSpaceSize となるような -XX:MaxMetaspaceSize を設定した際の、 CompressedClassSpaceSize のデフォルト 1GB からの調整内容をまとめると以下になります。
| バージョン |
MaxMetaspaceSize の設定値 |
調整前後の CompressedClassSpaceSize
|
調整内容 |
|---|---|---|---|
| Java 8 | 500MB | 1GB → 492MB | MaxMetaspaceSize - 8MB |
| Java 25 | 500MB | 1GB → 400MB | MaxMetaspaceSize * (8/10) |
Java 25 では Java 8 と比べると、Compressed Class Space 領域が Metaspace 領域を占有しないように、少し余裕を持って再調整する動作になっていることが分かりました。
上記のまとめは本記事内の動作確認の結果に基づくものです。
実行環境により異なる可能性がある点にはご注意ください。
おわりに
本記事では Java オプション -XX:CompressedClassSpaceSize について、動作確認で気になった内容を OpenJDK ソースコード実装から追ってみました。使用している Java のバージョンによって動作が異なる場合もあるので注意が必要です。Javadoc に記載のない仕様や内部動作の把握には OpenJDK ソースコードの確認がおすすめです。
記載されている会社名、製品名、サービス名は、各社の商標または登録商標です。