🐔

jlink コマンドでカスタム JRE 作った時は java -Xshare:dump で default CDS archive 作っとけ

2021/12/21に公開

この記事は Java Advent Calendar 2021 の第 10 日目が空いていたので、急遽ぶっこんだ記事である。

何の話?

出オチ。タイトルのまんま。

最近は JRE 配布してくんないから jlink コマンドでカスタム JRE 作ったりすると思うけど、そうすると default CDS archive が無い状態だから java -Xshare:dump して作っとけよって話。

あ、Java 11 までは JDK にも default CDS archive は付いてこないので、JDK 9~11 ならカスタム JRE 作って無くても java -Xshare:dump しといた方がいい。

何で jlink コマンドの話が出てくるの?

jlink コマンドを使ってカスタム JRE 作ると良い事があるよってのは別記事「Java アプリ用のコンテナイメージ作るんなら jlink コマンドでカスタム JRE 作っとけ」に書いたが、jlink コマンドでカスタム JRE 作った場合、default CDS archive が自動では作られないので作っといた方がいいよって事。

そもそも CDS って何?

そもそも CDS (Class Data Sharing)って何?って話はあちこちで見かけるので詳細は省くが、ざっくり言うと Java は起動がくっそおせぇから何とかしてやろう、っていう仕掛けのことだ。(いや、他にもメモリフットプリント削減とかの効果もあるけど…)

で、その仕掛けのために CDS archive っていうファイルがあって、そいつは「JVM がロードしたクラスをダンプした」みたいな感じのファイルらしく、このファイルを使うと jar とかからクラスをロードするよりも高速にクラスのロードが出来る、と言うわけだ。

default CDS archive って何?

CDS archive を使うと起動が速くなるんだが、ユーザがいちいち CDS archive を作るのは面倒だし、そもそもほとんどのヤツは CDS archive を作ると起動が速くなるなんて知らねぇし、だったら JDK に最初っから CDS archive を付けとこうぜ、と言う事で、デフォルトの CDS archive ファイルが作られるようになった。(JDK 12 あたりからか?)

じゃあその default CDS archive ってのは具体的にどのファイルの事?となるが、それは $JAVA_HOME ディレクトリ内の lib/server/classes.jsalib/server/classes_nocoops.jsa の 2 ファイルである。

何でファイルが 2 つもあるの?

2 つあるって事は 2 つとも使われるのか?と思うかもしれないが、CDS archive は実行時にはせいぜい 1 つしか参照されない。

じゃあ使われんのはどっちだよ、と言うと、JVM が CompressedOops で動いているかどうかでどちらを使うかが決まる。ここでピンときた人もいるかもしれない。CompressedOops で動いているときは lib/server/classes.jsa が使われて、CompressedOops で動いていない時は lib/server/classes_nocoops.jsa が使われる。"NO CompressedOOPS"、略して nocoops ってことやね。

つまり、CompressedOops の時は lib/server/classes_nocoops.jsa は要らないし、CompressedOops じゃない時は lib/server/classes.jsa は要らない。

CompressedOops が… lib/server/classes.jsa lib/server/classes_nocoops.jsa
有効 必要 不要
無効 不要 必要

ちなみに、CompressedOops が無効でも CDS が使えるようになったのは Java 14、ファイルが 2 つになったのは Java 15 あたりっぽい。

CompressedOops って何?

これもあちこちで見かけるので詳細は省くが、ざっくり言うと参照のサイズ(ポインタのサイズ)を圧縮してしまえ、と言うヤツだ。

JVM が 64 bit で動いている場合、参照のサイズは 64 bit、つまり 8 バイトになってしまうんだけど、ちょっとウマいことやれば 4 バイトですむよね、と言う感じ。

CompressedOops はデフォルトで使う事になっているが、ヒープサイズが 32 GB あたりから使わなく(使えなく)なる(境界はわりと微妙な値になっていると思う)。Java の起動オプションで明示的に指定することもできて、-XX:+UseCompressedOops だと使う、-XX:-UseCompressedOops だと使わないになる。

「そんなんどっちだか知らね~し」と言う向きは、Java の起動オプションに -XX:+PrintFlagsFinal を付けてもらえれば、最終的に使う事になっているのかどうかの表示が出るので確かめればよい。で、どちらになっているのかが分かれば、どちらのファイルが使われるのかも分かる。

ホントはどっちが使われているかを明示的に出力してくれればもっと嬉しいんだけど、残念ながら少なくとも現状はそうなっていない。

ちなみに、手元の環境では、最大ヒープサイズが 32736 MB までは CompressedOops が有効で、最大ヒープサイズをそれより 1 バイトでも大きくすると CompressedOops が無効になった。

# 32736 MB までだと UseCompressedOops が true になる
$ java -Xmx32736m -XX:+PrintFlagsFinal
[Global flags]
...
   size_t MaxHeapSize                              = 34326183936                               {product} {command line}
...
     bool UseCompressedOops                        = true                           {product lp64_product} {ergonomic}
...
# 32736 MB より 1 バイトでも大きいと UseCompressedOops が false になる
$ java -Xmx34326183937 -XX:+PrintFlagsFinal
[Global flags]
...
   size_t MaxHeapSize                              = 34342961152                               {product} {command line, ergonomic}
...
     bool UseCompressedOops                        = false                          {product lp64_product} {default}
...

また、最大ヒープサイズを 32736 MB より大きくした上で -XX:+UseCompressedOops も一緒に指定すると、警告が出て CompressedOops は有効にはならなかった。

# 32736 MB より 1 バイト大きい状態で -XX:+UseCompressedOops すると警告が出て、
# UseCompressedOops が勝手に false になる
$ java -Xmx34326183937 -XX:+UseCompressedOops -XX:+PrintFlagsFinal
OpenJDK 64-Bit Server VM warning: Max heap size too large for Compressed Oops
[Global flags]
...
   size_t MaxHeapSize                              = 34342961152                               {product} {command line, ergonomic}
...
     bool UseCompressedOops                        = false                          {product lp64_product} {command line}
...

せいぜい 1 つしか参照されないって事は、両方参照されない事もある?

両方参照されないという状況は、以下の 2 通りある。

  1. CDS を使用しない。
  2. デフォルトじゃない CDS アーカイブを使用する。

実際 CDS が有効かどうかも、-XX:+PrintFlagsFinal で分かる。
ただし、名前がちょっと難しくて(そうでもないか?)、UseSharedSpaces と表示される。

# デフォルトの場合
$ java -XX:+PrintFlagsFinal
...
     bool UseSharedSpaces                          = true                                      {product} {default}
...

CDS を使用しない

CDS はあくまでもオプション機能なので、使用しないこともできる。CDS を使用しない場合には、いずれのファイルも不要なのは言うまでもない。

なお、デフォルトでは lib/server/classes*.jsa のうち必要な方が使えれば On になるし、もしこのファイルが使えなければ Off になる。したがって、両方のファイルを削除してしまえば CDS は Off になるが、Java が起動できないわけではない(起動が多少遅くなるかもしれないが…)。

# classes*.jsa を削除した場合
$ rm $JAVA_HOME/lib/server/*.jsa
$ java -XX:+PrintFlagsFinal
...
     bool UseSharedSpaces                          = false                                     {product} {default}
...

また、Java の起動引数に Xshare:off を指定すればファイルがあっても強制的に Off にできる。

# -Xshare:off を付けた場合
$ java -Xshare:off -XX:+PrintFlagsFinal
...
     bool UseSharedSpaces                          = false                                     {product} {command line}
...

ちなみに、Java の起動引数に Xshare:on を指定すると強制的に On にしようとするが、この時 lib/server/classes*.jsa のうち必要な方が使えないと Java の起動に失敗する。

# classes.jsa を削除して -Xshare:on -XX:+UseCompressedOops を付けた場合
$ rm $JAVA_HOME/lib/server/classes.jsa
$ java -Xshare:on -XX:+UseCompressedOops
An error has occurred while processing the shared archive file.
Specified shared archive not found (/usr/local/openjdk-17/lib/server/classes.jsa).
Error occurred during initialization of VM
Unable to use shared archive.
# classes_nocoops.jsa を削除して -Xshare:on -XX:-UseCompressedOops を付けた場合
$ rm $JAVA_HOME/lib/server/classes_nocoops.jsa
$ java -Xshare:on -XX:-UseCompressedOops
An error has occurred while processing the shared archive file.
Specified shared archive not found (/usr/local/openjdk-17/lib/server/classes_nocoops.jsa).
Error occurred during initialization of VM
Unable to use shared archive.

デフォルトじゃない CDS を使用する

default CDS archive と言う名前から分かるように、lib/server/classes*.jsa は「デフォルトの」CSD archive である。て事は、当然デフォルトではない CDS archive も存在しうる。

デフォルトじゃない CDS archive は Java の起動オプションに -XX:SharedArchiveFile=ファイル名 を指定することで使用することができる。

で、結局何をすればいいの?

タイトルの通り java -Xshare:dump を叩けばいいんだが、上記の通りデフォルトファイルは 2 種類ある。

で、どっちが作られるかは使われる条件と一緒で CompressedOops が有効かどうかで決まるので、ぶっちゃけアプリケーション動かすときと同じオプションを指定した上で、プラスして -Xshare:dump オプションを指定するのが確実だと思う。

ちなみに、-Xshare:dump オプションを指定するとアプリケーションは起動せずに default CDS archive だけ生成して終了する。

でもデフォルトじゃない CDS archive 使ってる場合は要らないよね?

実は要らないとも言えない。て言うか、CDS archive は実行時にはせいぜい 1 つしか参照されないと言ったな。あれはウソだ。
ウソなんだが、いろいろややこしいので気力が足りずに今回は省略。申し訳 nothing。
説明する気力が沸いたら別途記事を書きたいと思う。

Discussion