Java の配列要素数の上限

Java で配列を生成する際に、どこまで大きい要素数を指定できるかは、Java VMの実装によるみたいです。
なので、OracleJava VM ではメモリが十分にあっても、次のコードは必ずエラーになります。

int[] max = new int[Integer.MAX_VALUE];


じゃあ、メモリが十分にある場合の上限がどこまでなのかというと、OracleJava VM の場合は以下の通りです*1

32bit の Java VM 0x3fffffff - 3 (= 1,073,741,820)
64bit の Java VM 0x7fffffff - 2 (= 2,147,483,645)

どういうこと?

まずは、Java 言語仕様を確認したところ、以下の記載がありました。

配列は int 値によってインデックスづけされなければならず、short, byte, char 値も単数の数値格上げによって int 値となるため、インデックス値として使用することができる。
(中略)
配列に対するすべてのアクセスは実行時にチェックされ、ゼロより小さい、あるいは配列の長さ以上のインデックスを使用しようとした場合、ArrayIndexOutOfBoundsException がスローされる。

10.4 配列へのアクセス - Java 言語仕様 第3版

要するに、配列のインデックスには 0 〜 Integer_MAX_VALUE (=0x7fffffff) まで使用できる、とのことです。
どこまで生成できるかについては、別の個所*2に、「(メモリを確保できないなら) OutOfMemoryError がスローされる」という記載があるだけです。


このことから、一見するとメモリが十分にあれば要素数が Integer.MAX_VALUE の配列まで生成できそうな気がしますが、そうではないようです。
実際にやってみると、OracleJava VM だと「VMの制限を超えている」というエラーになります。

java.lang.OutOfMemoryError: Requested array size exceeds VM limit

メモリ不足であれば「java.lang.OutOfMemoryError: Java heap space」ですが、それとは違うエラーです。

Java VM のコードで確認

Java VMのコードをダウンロードして、上記のメッセージで GREP してみました。
すると見つかったのはこんなコード。

objArrayOop arrayKlass::allocate_arrayArray(int n, int length, TRAPS) {
  if (length < 0) {
    THROW_0(vmSymbols::java_lang_NegativeArraySizeException());
  }
  if (length > arrayOopDesc::max_array_length(T_ARRAY)) {
    report_java_out_of_memory("Requested array size exceeds VM limit");
    JvmtiExport::post_array_size_exhausted();
    THROW_OOP_0(Universe::out_of_memory_error_array_size());
  }


arrayOopDesc::max_array_length 関数が、この OutOfMemoryError の発生条件のようです。
この関数が何かというと…。

  // Return the maximum length of an array of BasicType.  The length can passed
  // to typeArrayOop::object_size(scale, length, header_size) without causing an
  // overflow. We also need to make sure that this will not overflow a size_t on
  // 32 bit platforms when we convert it to a byte size.
  static int32_t max_array_length(BasicType type) {
    assert(type >= 0 && type < T_CONFLICT, "wrong type");
    assert(type2aelembytes(type) != 0, "wrong type");

    const size_t max_element_words_per_size_t =
      align_size_down((SIZE_MAX/HeapWordSize - header_size(type)), MinObjAlignment);
    const size_t max_elements_per_size_t =
      HeapWordSize * max_element_words_per_size_t / type2aelembytes(type);
    if ((size_t)max_jint < max_elements_per_size_t) {
      // It should be ok to return max_jint here, but parts of the code
      // (CollectedHeap, Klass::oop_oop_iterate(), and more) uses an int for
      // passing around the size (in words) of an object. So, we need to avoid
      // overflowing an int when we add the header. See CRs 4718400 and 7110613.
      return align_size_down(max_jint - header_size(type), MinObjAlignment);
    }
    return (int32_t)max_elements_per_size_t;
  }


よくわかりません!
ただ、HeapWordSize が 32bit と 64bit で違う*3ので、 32bit と 64bit のVMで挙動が違うはず、というのはわかりました。


ということで、冒頭に書いた結果になるようです。

*1:Java 7 Update 40 (Windows版)で確認

*2:15.10.3 例:配列生成とメモリ不測の検出

*3:HeapWordSize は、32bit なら 4、64bitなら 8。