LoginSignup
0
1

Java の -Xmx を減らしたら OutOfMemoryError が解消した話

Last updated at Posted at 2023-12-19

Java で OutOfMemoryError: Java heap space が発生する場合、-Xmx (最大ヒープ サイズ) を増やすほうが良いと思っていました。しかし、実際に -Xmx を減らすことで OutOfMemoryError が解消する経験をしましたのでご紹介します。

-Xmx を減らすと解消する例

対象とするプログラム

以下のようにたくさんのオブジェクトを格納する配列を使用するプログラムで発生しました。

class ObjectArrayAllocator {
    public static void main(String[] args) throws NumberFormatException {
        int gb = Integer.parseInt(args[0]);
        Object[][] objects = new Object[gb][1_000_000_000];
        System.out.printf(
            "allocated for %d objects%n",
            (long) objects.length * objects[0].length
        );
    }
}

実行してみる

JDK 17 で上記プログラムをコンパイルし、実行してみます。

$ java -Xmx40g ObjectArrayAllocator 7
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at ObjectArrayAllocator.main(ObjectArrayAllocator.java:4)
$ java -Xmx31g ObjectArrayAllocator 7
allocated for 7000000000 objects

-Xmx40g を指定すると OutOfMemoryError が発生するのに、-Xmx31g を指定すると正常に実行できることがわかります。

なぜ小さい方が良いのか

実は、オブジェクトのポインタのサイズは、デフォルトではヒープ サイズによって異なります。ヒープが約 32 GB 未満の場合はデフォルトで 32 bits の Compressed Oops が使われます。32 GB を超えるとポインタのサイズが 64 bits に倍増するため、上記の例では -Xmx40g を指定するとメモリが不足してしまうのです。(ただし、ZGC の場合には常に 64-bit ポインタが使われるそうです。)

32 GB を超えても 32-bit ポインタを使いたい場合

実は、32GB を超えても 32-bit ポインタを使う方法があります。

明示的に Compressed Oops を使いたいことを指定する

まず、-XX:+UseCompressedOops を指定することで、32-bit ポインタが使える場合には使うように指定します。しかし、これだけだと 32-bit ポインタが使えなかった場合は警告が出るだけで、実行は 64-bit ポインタを使って継続されてしまいます。

$ java -Xmx40g -XX:+UseCompressedOops ObjectArrayAllocator 7
OpenJDK 64-Bit Server VM warning: Max heap size too large for Compressed Oops
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at ObjectArrayAllocator.main(ObjectArrayAllocator.java:4)

さらに、ObjectAlignmentInBytes を指定する

そこで必要になるオプションが -XX:ObjectAlignmentInBytes です。

オブジェクトはデフォルトで 8 bits 単位で align されるのですが、Compressed Oops は「バイト単位なら末尾 3 bits はいつも 0」という事実を利用してデータを 3 bits シフトすることで、32 bits に 35 bits 分の情報を持たせています。

つまり、もし 16 bits 単位で align するならば Compressed Oops は 32 bits で 36 bits 分の情報を持つことができ、約 64 GB までのヒープ領域を使用することができます。

先ほどのプログラムで、80 億個のオブジェクトの領域を確保するように指定して実行してみます。

$ java -Xmx40g -XX:+UseCompressedOops ObjectArrayAllocator 8
OpenJDK 64-Bit Server VM warning: Max heap size too large for Compressed Oops
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at ObjectArrayAllocator.main(ObjectArrayAllocator.java:4)
$ java -Xmx40g -XX:+UseCompressedOops -XX:ObjectAlignmentInBytes=16 ObjectArrayAllocator 8
allocated for 8000000000 objects

-XX:ObjectAlignmentInBytes を指定することで、40 GB のヒープでも Compressed Oops を使い続けることができました。

-XX:ObjectAlignmentInBytes には 8 から 256 までの 2 の冪乗を指定できます。

参考

Compressed OOPs in the JVM

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1