0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

new int[n]のnの最大値

Posted at

Javaで配列をメモリ確保する場合、要素数の最大はいくつだろうか。

たまたま見つけた2013/09の記事では、32ビットJVMで0x3fffffff - 3 (= 1,073,741,820)、64ビットJVMで0x7fffffff - 2 (= 2,147,483,645)が実測値らしい。
2025/05時点では、64ビットJVMが一般的なので、64ビットのみを調べてみる。

Java の配列要素数の上限

インデックスの制限

配列の添字の値として、int値に限定しているので、コンパイル時の制限として、最大値は2,147,483,647(0xFFFF_FFFF)となる。

10.4 Array Access

Arrays must be indexed by int values
An attempt to access an array component with a long index value results in a compile-time error.
配列は,int値によってインデクス付けしなくてはならない。
longインデクス値によって配列構成要素をアクセスしようとすると,コンパイル時エラーが結果として起こる。

実際に確保できる要素数

二分探索でどこまでメモリ確保できるか実行してみる。

midの計算について、mid=(ng+ok)/2;と書くとオーバーフローするので、mid=(ng-ok)/2+ok;としたが、そういえばArrays.binarySearch0ではmid=(ng+ok)>>>1;と書いていて、論理シフトすることで上位ビット(符号ビット)が常に0を入れるということは、(ng+ok)の結果を一時的にsigned intでなくunsigned intとして扱うことになる。

Main.java
public class Main {
	public static void main(String[] args) {
		int ng=Integer.MAX_VALUE;
		int ok=0;
		while (ok+1<ng) {
//			int mid=(ng+ok)/2; //overflow 7FFF_FFFF+3FFF_FFFF=BFFF_FFFE=-1,073,741,826
//			int mid=(ng-ok)/2+ok; //success
			int mid=(ng+ok)>>>1; //success BFFF_FFFE>>>1=5FFF_FFFF
//			int mid=(ng+ok)>>1; //failed BFFF_FFFE>>1=DFFF_FFFF
            try {
				int[] ary=new int[mid];
				System.out.printf("OK %,d %08X (%08X %08X)\n", mid, mid, ok, ng);
				ok=mid;
			} catch (Error e) {
				System.out.printf("NG %,d %08X (%08X %08X)\n", mid, mid, ok, ng);
				ng=mid;
			}
		}
		System.out.printf("%,d %08X\n", ok, ok);
	}
}

java Mainの実行結果

起動時に何も-Xmxを指定しない場合、1,055,916,030(3EEF_FFFE)個だった。
int型は4バイトなので、4,223,664,120バイト+α(インスタンス型を管理するヘッダサイズ)がメモリ確保される。4,223,664,120B=4,124,671.99KiB=4027.99MiB=3.93GiB(切り捨て)

64ビットJVMだからこそ確保できるメモリ量で、32ビットのWindowsの場合、どれだけメモリを積んでいても、最大でもユーザアプリが2GiB(起動オプションで3GiBモードに変更可能)までなので、デフォルトで4GiB近く使える状態になっている。

環境
Intel Core i7 16GB
Windows10 22H2 64ビット
java version "17.0.9" 2023-10-17 LTS 64ビット

NG 1,073,741,823 3FFFFFFF (00000000 7FFFFFFF)
OK 536,870,911 1FFFFFFF (00000000 3FFFFFFF)
OK 805,306,367 2FFFFFFF (1FFFFFFF 3FFFFFFF)
OK 939,524,095 37FFFFFF (2FFFFFFF 3FFFFFFF)
OK 1,006,632,959 3BFFFFFF (37FFFFFF 3FFFFFFF)
OK 1,040,187,391 3DFFFFFF (3BFFFFFF 3FFFFFFF)
NG 1,056,964,607 3EFFFFFF (3DFFFFFF 3FFFFFFF)
OK 1,048,575,999 3E7FFFFF (3DFFFFFF 3EFFFFFF)
OK 1,052,770,303 3EBFFFFF (3E7FFFFF 3EFFFFFF)
OK 1,054,867,455 3EDFFFFF (3EBFFFFF 3EFFFFFF)
NG 1,055,916,031 3EEFFFFF (3EDFFFFF 3EFFFFFF)
OK 1,055,391,743 3EE7FFFF (3EDFFFFF 3EEFFFFF)
OK 1,055,653,887 3EEBFFFF (3EE7FFFF 3EEFFFFF)
OK 1,055,784,959 3EEDFFFF (3EEBFFFF 3EEFFFFF)
OK 1,055,850,495 3EEEFFFF (3EEDFFFF 3EEFFFFF)
OK 1,055,883,263 3EEF7FFF (3EEEFFFF 3EEFFFFF)
OK 1,055,899,647 3EEFBFFF (3EEF7FFF 3EEFFFFF)
OK 1,055,907,839 3EEFDFFF (3EEFBFFF 3EEFFFFF)
OK 1,055,911,935 3EEFEFFF (3EEFDFFF 3EEFFFFF)
OK 1,055,913,983 3EEFF7FF (3EEFEFFF 3EEFFFFF)
OK 1,055,915,007 3EEFFBFF (3EEFF7FF 3EEFFFFF)
OK 1,055,915,519 3EEFFDFF (3EEFFBFF 3EEFFFFF)
OK 1,055,915,775 3EEFFEFF (3EEFFDFF 3EEFFFFF)
OK 1,055,915,903 3EEFFF7F (3EEFFEFF 3EEFFFFF)
OK 1,055,915,967 3EEFFFBF (3EEFFF7F 3EEFFFFF)
OK 1,055,915,999 3EEFFFDF (3EEFFFBF 3EEFFFFF)
OK 1,055,916,015 3EEFFFEF (3EEFFFDF 3EEFFFFF)
OK 1,055,916,023 3EEFFFF7 (3EEFFFEF 3EEFFFFF)
OK 1,055,916,027 3EEFFFFB (3EEFFFF7 3EEFFFFF)
OK 1,055,916,029 3EEFFFFD (3EEFFFFB 3EEFFFFF)
OK 1,055,916,030 3EEFFFFE (3EEFFFFD 3EEFFFFF)
1,055,916,030 3EEFFFFE

java -Xmxを指定して比較

-Xmx16g

起動時に-Xmx16gを指定した場合、2,147,483,645(7FFF_FFFD)個だった。
4倍すると、8,589,934,580B=8,388,607.98KiB=8,191.99MiB=7.99GiB(切り捨て)

OK 1,073,741,823 3FFFFFFF (00000000 7FFFFFFF)
OK 1,610,612,735 5FFFFFFF (3FFFFFFF 7FFFFFFF)
OK 1,879,048,191 6FFFFFFF (5FFFFFFF 7FFFFFFF)
OK 2,013,265,919 77FFFFFF (6FFFFFFF 7FFFFFFF)
OK 2,080,374,783 7BFFFFFF (77FFFFFF 7FFFFFFF)
OK 2,113,929,215 7DFFFFFF (7BFFFFFF 7FFFFFFF)
OK 2,130,706,431 7EFFFFFF (7DFFFFFF 7FFFFFFF)
OK 2,139,095,039 7F7FFFFF (7EFFFFFF 7FFFFFFF)
OK 2,143,289,343 7FBFFFFF (7F7FFFFF 7FFFFFFF)
OK 2,145,386,495 7FDFFFFF (7FBFFFFF 7FFFFFFF)
OK 2,146,435,071 7FEFFFFF (7FDFFFFF 7FFFFFFF)
OK 2,146,959,359 7FF7FFFF (7FEFFFFF 7FFFFFFF)
OK 2,147,221,503 7FFBFFFF (7FF7FFFF 7FFFFFFF)
OK 2,147,352,575 7FFDFFFF (7FFBFFFF 7FFFFFFF)
OK 2,147,418,111 7FFEFFFF (7FFDFFFF 7FFFFFFF)
OK 2,147,450,879 7FFF7FFF (7FFEFFFF 7FFFFFFF)
OK 2,147,467,263 7FFFBFFF (7FFF7FFF 7FFFFFFF)
OK 2,147,475,455 7FFFDFFF (7FFFBFFF 7FFFFFFF)
OK 2,147,479,551 7FFFEFFF (7FFFDFFF 7FFFFFFF)
OK 2,147,481,599 7FFFF7FF (7FFFEFFF 7FFFFFFF)
OK 2,147,482,623 7FFFFBFF (7FFFF7FF 7FFFFFFF)
OK 2,147,483,135 7FFFFDFF (7FFFFBFF 7FFFFFFF)
OK 2,147,483,391 7FFFFEFF (7FFFFDFF 7FFFFFFF)
OK 2,147,483,519 7FFFFF7F (7FFFFEFF 7FFFFFFF)
OK 2,147,483,583 7FFFFFBF (7FFFFF7F 7FFFFFFF)
OK 2,147,483,615 7FFFFFDF (7FFFFFBF 7FFFFFFF)
OK 2,147,483,631 7FFFFFEF (7FFFFFDF 7FFFFFFF)
OK 2,147,483,639 7FFFFFF7 (7FFFFFEF 7FFFFFFF)
OK 2,147,483,643 7FFFFFFB (7FFFFFF7 7FFFFFFF)
OK 2,147,483,645 7FFFFFFD (7FFFFFFB 7FFFFFFF)
NG 2,147,483,646 7FFFFFFE (7FFFFFFD 7FFFFFFF)
2,147,483,645 7FFFFFFD

最初の3FFFFFFFがOKとなってくれないと、3FFF_FFFF+7FFF_FFFF=BFFF_FFFE=-1,073,741,826から論理シフトでオーバーフローを回避する確認にならなかったが、ちゃんと5FFFFFFF=1,610,612,735となっている。

もしBFFF_FFFEを負の数として2で割るとDFFF_FFFF=-536,870,913になる。BFFF_FFFEは最下位ビットが0のため、負の数として2で割っても、算術シフトしても同じ結果となる。
実際、ソース修正してmidが負の数として扱うと、java.lang.NegativeArraySizeException: -536870913となる。

-Xmx8g

起動時に-Xmx8gを指定した場合、2,145,386,492(7FDF_FFFC)個だった。
4倍すると、8,581,545,968B=8,380,415.98KiB=8,183.99MiB=7.99GiB(切り捨て)

-Xmx4g

起動時に-Xmx4gを指定した場合、1,072,168,958(3FE7_FFFE)個だった。
4倍すると、4,288,675,832B=4,188,159.99KiB=4,089.99MiB=3.99GiB(切り捨て)

-Xmx2g

起動時に-Xmx2gを指定した場合、536,084,478(1FF3_FFFE)個だった。
4倍すると、2,144,337,912B=2,094,079.99KiB=2,044.99MiB=1.99GiB(切り捨て)

-Xmx1g

起動時に-Xmx1gを指定した場合、267,517,950(0FF1_FFFE)個だった。
4倍すると、1,070,071,800B=1,044,991.99KiB=1,020.49MiB=0.99GiB(切り捨て)

-Xmx512m

起動時に-Xmx512mを指定した場合、133,693,436(07F7_FFFC)個だった。
4倍すると、534,773,744B=522,239.98KiB=509.99MiB=0.49GiB(切り捨て)

ヒープサイズごとの比較

起動オプションごとに、intの個数、4倍したバイト数、KiB、MiB、GiBをまとめた。
2Gを指定すれば1.99GB確保できるというのは当たり前のようで、むかしのバージョンのJVMはメタスペースがヒープとは別途必要で、ヒープとして1.6GB程度しか使えなかった。

サイズ 個数 バイト Kiバイト Miバイト Giバイト
無し 1,055,916,030 4,223,664,120 4,124,671.99 4027.99 3.93
-Xmx16g 2,147,483,645 8,589,934,580 8,388,607.98 8,191.99 7.99
-Xmx8g 2,145,386,492 8,581,545,968 8,380,415.98 8,183.99 7.99
-Xmx4g 1,072,168,958 4,288,675,832 4,188,159.99 4,089.99 3.99
-Xmx2g 536,084,478 2,144,337,912 2,094,079.99 2,044.99 1.99
-Xmx1g 267,517,950 1,070,071,800 1,044,991.99 1,020.49 0.99
-Xmx512m 133,693,436 534,773,744 522,239.98 509.99 0.49

データ型を変更して比較

byte[]の場合

ソースのint[] ary=new int[mid]byte[] ary=new byte[mid];に修正する。

起動時に何も-Xmxを指定しない場合、2,147,483,645(7FFF_FFFD)個だった。
2,147,483,645B=2,097,151.99KiB=2,047.99MiB=1.99GiB(切り捨て)

起動時に-Xmx1gを指定した場合、1,071,644,656(3FDF_FFF0)個だった。
1,071,644,656B=1,046,527.98KiB=1,021.99MiB=0.99GiB(切り捨て)

起動時に-Xmx3gを指定した場合、2,147,483,645(7FFF_FFFD)個だった。
2,147,483,645B=2,097,151.99KiB=2,047.99MiB=1.99GiB(切り捨て)

int型の場合の-Xmx16gと同じ個数が確保できているが、理論値7FFF_FFFFよりも2個少ないのは、管理上の何かがありそう。

boolean[]の場合

ソースのint[] ary=new int[mid]boolean[] ary=new boolean[mid];に修正する。

起動時に何も-Xmxを指定しない場合、2,147,483,645(7FFF_FFFD)個だった。
2,147,483,645B=2,097,151.99KiB=2,047.99MiB=1.99GiB(切り捨て)

起動時に-Xmx1gを指定した場合、1,071,644,656(3FDF_FFF0)個だった。
1,071,644,656B=1,046,527.98KiB=1,021.99MiB=0.99GiB(切り捨て)

起動時に-Xmx3gを指定した場合、2,147,483,645(7FFF_FFFD)個だった。
2,147,483,645B=2,097,151.99KiB=2,047.99MiB=1.99GiB(切り捨て)

結果はbyte[]と同じ。
booleanはtrue/falseの1ビットあれば管理できるが、配列にしても1要素1バイトを必要とする。1

short[]、char[]の場合

ソースのint[] ary=new int[mid]short[] ary=new short[mid];に修正する。

起動時に何も-Xmxを指定しない場合、2,112,880,632(7DEF_FFF8)個だった。
2倍すると、4,225,761,264B=4,126,719.98KiB=4,029.99MiB=3.93GiB(切り捨て)

起動時に-Xmx1gを指定した場合、535,822,328(1FEF_FFF8)個だった。
2倍すると、1,071,644,656B=1,046,527.98KiB=1,021.99MiB=0.99GiB(切り捨て)

起動時に-Xmx5gを指定した場合、2,147,483,645(7FFF_FFFD)個だった。
2倍すると、4,294,967,290B=4,194,303.99KiB=4,095.99MiB=3.99GiB(切り捨て)

ソースのint[] ary=new int[mid]char[] ary=new char[mid];に修正する。

起動時に何も-Xmxを指定しない場合と、起動時に-Xmx8gを指定した場合は、short[]とchar[]の違いはなかった。
起動時に-Xmx1gを指定した場合は、short[]とchar[]に若干違いは出たが、そもそもshort[]を何度も実行すると違いが出た。

int[]、float[]の場合

int[]について、初回で-Xmx1gの場合、267,517,950(0FF1_FFFE)個1,070,071,800バイトだったが、他の型で1,071,644,656バイトまで行けそうなので、5回ほどリトライしたら267,911,164(0FF7_FFFC)個1,071,644,656バイトとなった。

ソースのint[] ary=new int[mid]float[] ary=new float[mid];に修正する。

起動時に-Xmx16gを指定した場合は、int[]とfloat[]の違いはなかった。
起動時に何も-Xmxを指定しない場合と、起動時に-Xmx1gを指定した場合は、若干違いは出るが、int[]と同様に-Xmx1gの場合、267,517,950(0FF1_FFFE)個1,070,071,800バイトとなった。

long[]の場合

ソースのint[] ary=new int[mid]long[] ary=new long[mid];に修正する。

起動時に何も-Xmxを指定しない場合、528,220,158(1F7B_FFFE)個だった。
8倍すると、4,225,761,264B=4,126,719.98KiB=4,029.99MiB=3.93GiB(切り捨て)

起動時に-Xmx1gを指定した場合、133,955,582(07FB_FFFE)個だった。
8倍すると、1,071,644,656B=1,046,527.98KiB=1,021.99MiB=0.99GiB(切り捨て)

起動時に-Xmx16gを指定した場合、2,145,386,494(7FDFFFFE)個だった。2
8倍すると、17,163,091,952B=16,760,831.98KiB=16,367.99MiB=15.98GiB(切り捨て)

OK 1,073,741,823 3FFFFFFF (00000000 7FFFFFFF)
OK 1,610,612,735 5FFFFFFF (3FFFFFFF 7FFFFFFF)
OK 1,879,048,191 6FFFFFFF (5FFFFFFF 7FFFFFFF)
OK 2,013,265,919 77FFFFFF (6FFFFFFF 7FFFFFFF)
OK 2,080,374,783 7BFFFFFF (77FFFFFF 7FFFFFFF)
OK 2,113,929,215 7DFFFFFF (7BFFFFFF 7FFFFFFF)
OK 2,130,706,431 7EFFFFFF (7DFFFFFF 7FFFFFFF)
OK 2,139,095,039 7F7FFFFF (7EFFFFFF 7FFFFFFF)
OK 2,143,289,343 7FBFFFFF (7F7FFFFF 7FFFFFFF)
NG 2,145,386,495 7FDFFFFF (7FBFFFFF 7FFFFFFF)
OK 2,144,337,919 7FCFFFFF (7FBFFFFF 7FDFFFFF)
OK 2,144,862,207 7FD7FFFF (7FCFFFFF 7FDFFFFF)
OK 2,145,124,351 7FDBFFFF (7FD7FFFF 7FDFFFFF)
OK 2,145,255,423 7FDDFFFF (7FDBFFFF 7FDFFFFF)
OK 2,145,320,959 7FDEFFFF (7FDDFFFF 7FDFFFFF)
OK 2,145,353,727 7FDF7FFF (7FDEFFFF 7FDFFFFF)
OK 2,145,370,111 7FDFBFFF (7FDF7FFF 7FDFFFFF)
OK 2,145,378,303 7FDFDFFF (7FDFBFFF 7FDFFFFF)
OK 2,145,382,399 7FDFEFFF (7FDFDFFF 7FDFFFFF)
OK 2,145,384,447 7FDFF7FF (7FDFEFFF 7FDFFFFF)
OK 2,145,385,471 7FDFFBFF (7FDFF7FF 7FDFFFFF)
OK 2,145,385,983 7FDFFDFF (7FDFFBFF 7FDFFFFF)
OK 2,145,386,239 7FDFFEFF (7FDFFDFF 7FDFFFFF)
OK 2,145,386,367 7FDFFF7F (7FDFFEFF 7FDFFFFF)
OK 2,145,386,431 7FDFFFBF (7FDFFF7F 7FDFFFFF)
OK 2,145,386,463 7FDFFFDF (7FDFFFBF 7FDFFFFF)
OK 2,145,386,479 7FDFFFEF (7FDFFFDF 7FDFFFFF)
OK 2,145,386,487 7FDFFFF7 (7FDFFFEF 7FDFFFFF)
OK 2,145,386,491 7FDFFFFB (7FDFFFF7 7FDFFFFF)
OK 2,145,386,493 7FDFFFFD (7FDFFFFB 7FDFFFFF)
OK 2,145,386,494 7FDFFFFE (7FDFFFFD 7FDFFFFF)
2,145,386,494 7FDFFFFE

物理メモリ64GBの環境

long[]の-Xmx16Gが、2,145,386,495(7FDF_FFFF)でNGとなったのが、物理メモリが足りていない理由か、物理メモリに関係ないのか気になったので、物理メモリ64GBの別筐体で実行する。

起動時に何も-Xmxを指定しない場合、2,140,143,614(7F8F_FFFE)個だった。
8倍すると、17,121,148,912B=16,719,871.98KiB=16,327.99MiB=15.94GiB(切り捨て)

起動時に-Xmx16gを指定した場合、2,145,386,494(7FDF_FFFE)個だった。
8倍すると、17,163,091,952B=16,760,831.98KiB=16,367.99MiB=15.98GiB(切り捨て)
2,145,386,495(7FDF_FFFF)でNGとなったので、物理メモリか仮想メモリの違いはなさそう。

起動時に-Xmx17gを指定した場合、2,147,483,645(7FFF_FFFD)個だった。
8倍すると、17,179,869,160B=16,777,215.97KiB=16,383.99MiB=15.99GiB(切り捨て)

double[]の場合

ソースのint[] ary=new int[mid]double[] ary=new double[mid];に修正する。

起動時に何も-Xmxを指定しない場合、long[]とdouble[]の違いはなかった。
起動時に-Xmx1gを指定した場合、若干違いは出るが、最大値は133,955,582(07FB_FFFE)個だった。
起動時に-Xmx17gを指定した場合、2,147,483,645(7FFF_FFFD)個だった。

データ型ごとの比較

これまでの結果をピックアップしてまとめた。

個数MAXの2,147,483,645個となるのがshortだと5gとしているが、4gでは少し足りず、試しにm単位で刻んだら、-Xmx4104mが2,147,483,640(7FFFFFF8)個、-Xmx4105mが2,147,483,645(7FFFFFFD)個となった。

サイズ 個数 バイト Kiバイト Miバイト Giバイト
-Xmxなし(物理16GB) - - - - -
boolean,byte 2,147,483,645 2,147,483,645 2,097,151.99 2,047.99 1.99
short,char 2,112,880,632 4,225,761,264 4,126,719.98 4,029.99 3.93
int,float 1,055,916,030 4,223,664,120 4,124,671.99 4027.99 3.93
long,double 528,220,158 4,225,761,264 4,126,719.98 4,029.99 3.93
-Xmx1g指定 - - - - -
boolean,byte -Xmx1g 1,071,644,656 1,071,644,656 1,046,527.98 1,021.99 0.99
short,char -Xmx1g 535,822,328 1,071,644,656 1,046,527.98 1,021.99 0.99
int,float -Xmx1g 267,911,164 1,071,644,656 1,046,527.98 1,021.99 0.99
long,double -Xmx1g 133,955,582 1,071,644,656 1,046,527.98 1,021.99 0.99
個数MAX - - - - -
boolean,byte -Xmx3g 2,147,483,645 2,147,483,645 2,097,151.99 2,047.99 1.99
short,char -Xmx5g 2,147,483,645 4,294,967,290 4,194,303.99 4,095.99 3.99
int,float -Xmx9g 2,147,483,645 8,589,934,580 8,388,607.98 8,191.99 7.99
long,double -Xmx17g 2,147,483,645 17,179,869,160 16,777,215.97 16,383.99 15.99

まとめ

64ビットJVM(17.0.9)で分かったこと。

  • 配列の1要素あたり、byte,booleanは1バイト、short,charは2バイト、int,floatは4バイト、long,doubleは8バイトのメモリを必要とする。3
  • 起動時にヒープメモリサイズを指定しないと物理メモリの4分の1相当まで確保できる。(これの詳しい計算式がどこかにあったはず。物理メモリが少ないときは違う値のはず)
  • ソースコードに記述できる限界は、Integer.MAX_VALUE=2,147,483,647(7FFF_FFFF)個だが、実際には2個少ない2,147,483,645(7FFF_FFFD)個までしかメモリ確保できない。
  • ヒープサイズがギリギリの場合、ヘッダサイズやその他のオーバーヘッドがあるので、2,147,483,645(7FFF_FFFD)個より若干少なくなる。
  1. JLSを調べたら、boolean型を何バイトで管理するか記述はなかった。

  2. ただし、16GBしか物理メモリを積んでいないので、仮想メモリを使いまくり。-Xmx1gでは1.966秒程度の実行時間が、-Xmx16gでは882.319秒かかりました。

  3. 2値の場合は自然とboolean[]を使うが、3値となったらint[]にしてしまうが、byte[]の方が必要とするメモリは少ない。ただし、byte、short、charはintに昇格して演算することが多いので、バイト数が少ないから速くなるわけではない。

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?