Javaの外からJavaにチェックポイントを設ける

Red Hat で Solution Architect として OpenJDK を担当している伊藤ちひろ(@chiroito)です。

この記事は、Red Hat Developerのブログ記事、Checkpointing Java from outside of Java | Red Hat Developer の翻訳記事です。


https://developers.redhat.com/sites/default/files/styles/article_feature/public/blog/2020/10/2020_Java_Checkpointing_Featured_Article_A.png?itok=ka0eQv0Z

OpenJDKのJava仮想マシン(JVM)がJavaアプリケーションを実行する際、メインクラスを起動する前に十数個のクラスをロードします。また、あるメソッドを数百回実行してから、そのメソッドの最適化コンパイラを起動します。このような準備は、「write once, run anywhere(一度書けばどこでも実行できる)」というJavaのパワーを支える重要な要素ですが、その代償として起動時間が長くなってしまいます。

私たちは、新しいアプローチに取り組んできました。これにより、クラスをロードし、ジャストインタイム(JIT)コンパイラをウォームアップしてから、アプリケーションにチェックポイントを設けられます。その後、アプリケーションをリストアすることで、素早く実行できます。この変更により、起動に数秒かかっていたアプリケーションが、ミリ秒でウォームアップするようになりました。

この記事では、Linuxのコマンドラインからチェックポイントを設ける方法および実行中のJavaプログラムをリストアする方法をご紹介します。近日中に別の記事で、Javaコードの内部からJavaプログラムのチェックポイントを設けてリストアを可能にするJava Native Interface(JNI)ライブラリを紹介します。

Javaコードでのチェックポイントの使用

JNIのCheckpoint Restore libraryは、Linux Checkpoint/Restore in Userspace (CRIU)をベースにしています。この記事の例では、これを使用します。 CRIUは起動時間を短縮できますが、それ以上の可能性を秘めています。

長時間動作するプログラムがある場合、定期的にチェックポイントを設定できます。障害が発生した場合、最後のチェックポイントからアプリケーションを再起動できます。もし、障害の原因がバグであれば、すぐに再現できます。また、外部要因による障害であれば、作業を中断することなく、前回の続きを行えます。

別の例として、プログラムのいくつかのポイントでヒープダンプを取りたいが、ヒープ内を走査するために停止するとプログラムの実行が妨害されます。チェックポイントを挿入することで、プログラムを完了まで実行した後、戻ってヒープダンプを行うために再起動できます。これにより、元のプログラムと非常によく似たプログラムの実行順序で、興味のある時点でのメモリレイアウトを見れます。

いい感じでしょうか?例を見てみましょう。

Java以外からのチェックポイントを設ける処理

この例では、実行中のJavaプログラムをコマンドラインからチェックポイントを設け、リストアする方法を学びます。まず始めに、ScoobyというJavaプログラムを実行しているとします。

ターミナル1から、以下を入力します。

% setsid java -XX:-UsePerfData -XX:+UseSerialGC Scooby

別のターミナルの別のディレクトリから、以下を入力します。

% sudo criu dump -t <pid> --shell-job -o dump.log

これで ps を実行して、自分の Java プログラムがもはや実行されていないことを確認できます。ディレクトリを見ると、いくつかのイメージファイルを見られます。また、dump.log を見ると、CRIU がコードにチェックポイントを設けるために行ったことをすべて確認できます。

では、イメージをダンプしたディレクトリから、以下のようにしてください。

% sudo criu restore --shell-job -d -vvv -o restore.log

Javaプログラムが再び実行されているのが見えるはずです。restore.logをチェックして、リストアがどのように行われたかを確認できます。デフォルトでは、CRIUはJVMを同じプロセスID(PID)にリストアしていることに気づくでしょう。同じイメージを複数回リストアしたい場合は、仮想PIDを使用できます。

% sudo unshare -p -m -f bash
# mount -t proc none /proc/
# criu restore --shell-job

別のウィンドウで、同じディレクトリを使って、次のようにします。

% sudo unshare -p -m -f bash
# mount -t proc none /proc/
# criu restore  --shell-job

まとめ

チェックポイントを設けることにはいくつかの問題があります。今のところ、チェックポイントを使用する際には perf と並列ガベージコレクションをオフにする必要があります。また、/var/lib/sss/pipes/nss というファイルがある場合は、それを削除しなければなりません。また、restore 操作を実行するには root 権限が必要になります。なぜなら、特定の PID を選択できるようにする必要があるためです。現在、CRIUチームがこの問題に取り組んでいます。

次回は、JNI Checkpoint Restoreライブラリを使って、Javaの中からJavaにチェックポイントを設ける方法をご紹介しますので、お楽しみに。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。