torutkのブログ

ソフトウェア・エンジニアのブログ

Java読書会「Java Memory Management」を読む会(第3回)開催

本日、Java読書会「Java Memory Management」を読む会(第3回)開催

ページ数が120ページと少ないこと、PDF版のGoogle翻訳で日本語朗読したことで、3回で読了となりました。

洋書でもPDF版が入手できると読書会の進行が日本語書籍並みになるのが良いですね。 日本語書籍では、なかなか読書会向きの候補がないのですが、英語を候補範囲にすると大きく広がります。

脱線気味のメモ

読書会の中で、少し脱線気味なアイテムのメモです。

ガベージコレクションとスレッド数

Javaプロセスは、使用するCPU数、メモリサイズがデフォルトでは大きいので、リソースを絞って実行したいときはJVMオプションで構成を変更します。書籍では、-XX:ParallelGCThreads オプションが紹介されていました。

MacBook Pro 2020(M1チップ)でのParallelGCThreadsのデフォルト値は8でした。 CPUが8コア(Pコア4、Eコア4)で、Javaから見えるCPUコア数は8となっています。

jshell> Runtime.getRuntime().availableProcessors()
$1 ==> 8

ParallelGCThreadsは、CPUコア数が8以上のときは8がデフォルト値となりますが、マシンのコアをフルに使いたくない場合はJVMオプションで少ない数を指定しておくのが良いです。

昨今のJDKでは、GCが多数用意され、実行時にどれを使うか選択ができます。

GC種類 GC指定オプション 備考
SerialGC -XX:+UseSerialGC
ParallelGC -XX:+UseParallelGC
G1GC -XX:+UseG1GC デフォルトのGC
ZGC -XX:+UseZGC

なお、複数のGCを指定すると、java起動時にエラーとなってしまいます。

 % java -XX:+UseSerialGC -XX:+UseParallelGC -version
 Error occurred during initialization of VM
 Multiple garbage collectors selected

書籍では記載が見当たらなかったGCに関するもう一つのオプションが-XX:ConcGCThreadsです。 ParallelGCは、アプリケーションスレッドを停止するstop-the-worldな処理に使うスレッド数の指定、ConcGCThreadsは、アプリケーションスレッドを実行しながら並行して行うGC処理に使うスレッド数の指定となります。

ガベージコレクションとそのオプションについては、オラクルの日本語ドキュメントが役立ちます。

ガベージ・コレクションのチューニングの概要

Java ドキュメント(ダウンロード版)の日本語版は?

Java SEのドキュメントは、オンラインとダウンロード版がありますが、日本語のダウンロード版は次のサイトから入手できます。

Java SE 日本語ドキュメント | Oracle 日本

ミリ秒未満のウェイトを入れたい

Java Memory Management」7章で、メモリリークするプログラムに VisualVM を接続してヒープ使用量とGCの活動をモニターする例を紹介しています。 実際に書籍のサンプルプログラムを動かして VisualVM から接続しようとしましたが、プログラムを起動するとあっという間にOutOfMemoryErrorを発生します。サンプルプログラムは、次のようにforループ内でノーウェイトでオブジェクトを生成するものです。

       List<Person> list = new ArrayList<>();
        while(true) {
            Person p = new Person("John");
            list.add(p);
        }

そこで、ループにウェイトを噛ませようとしました。定番のThread.sleepはウェイトが1ミリ秒以上となるため、1秒間に1000オブジェクトしか生成できず、今度はいつまでたってもOutOfMemoryErrorになりません。

Javaには、1ミリ秒未満の短い時間をウェイトする方法があるかを探ってみました。APIとしては、マイクロ秒やナノ秒を渡すsleepもあります。

  • Threadクラスのsleepメソッド
    • public static void sleep(long millis, int nanos) throws InterruptedException
  • TimeUnit.MICROSECONDS、TimeUnit.NANOSECONDSのsleepメソッド

これらのAPIに1マイクロ秒や100マイクロ秒を渡してみましたが、実際には1ミリ秒のウェイトになってしましました。 これは、通常のOSのスケジューラーが1msのtickで動作するので、それ以下の短い時間をsleep(ウェイト)させることができず、ビジーループで実現するしかないかと思われます。

以下は、1,000 ns のビジーループメソッドです。

    static void busyWait() {
        var start = System.nanoTime();
        while (System.nanoTime() - start < 1000);
    }
JavaVMオプションを環境変数で渡す

コマンドラインオプションで渡すJVM構成の指定を、環境変数から行うこともできるようです。

Oracle JDKドキュメントのツール仕様で、javaコマンドに次の記載があります。

JDK_JAVA_OPTIONSは、その内容をコマンド行から解析されるオプションの先頭に追加します。

https://docs.oracle.com/javase/jp/20/docs/specs/man/java.html#using-the-jdk_java_options-launcher-environment-variable

JAVA_TOOL_OPTIONS

Oracle JDKドキュメントのトラブルシューティングガイドに記載があります。

環境変数とシステム・プロパティ

JDK_JAVA_OPTIONSとの違いは、javaコマンド以外の時にも反映できる点、メインクラスなどのJavaVMオプション以外も渡せる点です。

Mission ControlツールでもVisaulVMと同じような情報が得られるのか?

T.B.D.