KÍCH THƯỚC MẢNG ĐƯỢC YÊU CẦU VƯỢT QUÁ GIỚI HẠN VM

Java đã có một giới hạn về kích thước mảng tối đa mà chương trình của bạn có thể phân bổ. Giới hạn chính xác là nền tảng cụ thể nhưng thường ở đâu đó giữa 1 và 2,1 tỷ phần tử.1

Khi bạn đối mặt java.lang.OutOfMemoryError: Yêu cầu kích thước mảng vượt quá giới hạn VM, điều này có nghĩa là ứng dụng bị lỗi với lỗi đang cố gắng phân bổ một mảng lớn hơn Java Virtual Machine có thể hỗ trợ.

Vậy nguyên nhân gây ra lỗi đó là gì?

Lỗi được ném ra bởi mã gốc trong JVM. Nó xảy ra trước khi phân bổ bộ nhớ cho một mảng khi JVM thực hiện một kiểm tra nền tảng cụ thể: liệu cấu trúc dữ liệu được phân bổ có thể định vị được trong nền tảng này. Lỗi này ít phổ biến hơn bạn nghĩ ban đầu.

Lý do bạn hiếm khi gặp lỗi này là các mảng Java được lập chỉ mục bởi int. Int tích cực tối đa trong Java là 2 ^ 31 – 1 = 2,147,483,647. Và các giới hạn nền tảng cụ thể có thể thực sự gần với con số này – ví dụ trên 64bit MB Pro của tôi trên Java 1.7 Tôi có thể khởi tạo các mảng có khả năng khởi tạo mảng lên tới 2,147,483,645 hoặc Integer.MAX_VALUE-2.

Tăng chiều dài của mảng một đến Integer.MAX_VALUE-1 kết quả trong OutOfMemoryError quen thuộc:

Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit

Nhưng giới hạn có thể không cao ở Linux 32-bit với OpenJDK 6, bạn sẽ nhấn vào “java.lang.OutOfMemoryError: Yêu cầu kích thước mảng vượt quá giới hạn VM” khi phân bổ một mảng với ~ 1.1 tỷ phần tử. Để hiểu được giới hạn của các môi trường cụ thể của bạn, hãy chạy chương trình thử nghiệm nhỏ được mô tả trong chương tiếp theo.

Ví dụ:

Khi cố gắng tạo lại java.lang.OutOfMemoryError: Yêu cầu kích thước mảng vượt quá giới hạn lỗi VM, hãy nhìn vào đoạn mã sau:

for (int i = 3; i >= 0; i--) {
	try {
		int[] arr = new int[Integer.MAX_VALUE-i];
		System.out.format("Successfully initialized an array with %,d elements.\n", Integer.MAX_VALUE-i);
	} catch (Throwable t) {
		t.printStackTrace();
	}
}

Ví dụ lặp đi lặp lại bốn lần và khởi tạo một mảng các nguyên tố dài trên mỗi lượt. Kích thước của mảng mà chương trình này đang cố gắng khởi tạo phát triển bởi một lần với mỗi lần lặp và cuối cùng đạt tới Integer.MAX_VALUE. Bây giờ, khi khởi tạo đoạn mã trên Mac OS X 64 bit với Hotspot 7, bạn sẽ nhận được kết quả tương tự như sau:

java.lang.OutOfMemoryError: Java heap space
	at eu.plumbr.demo.ArraySize.main(ArraySize.java:8)
java.lang.OutOfMemoryError: Java heap space
	at eu.plumbr.demo.ArraySize.main(ArraySize.java:8)
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
	at eu.plumbr.demo.ArraySize.main(ArraySize.java:8)
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
	at eu.plumbr.demo.ArraySize.main(ArraySize.java:8)

Lưu ý rằng trước khi đối mặt java.lang.OutOfMemoryError: Yêu cầu kích thước mảng vượt quá giới hạn VM trong hai lần thử cuối cùng, phân bổ không thành công với java.lang.OutOfMemoryError quen thuộc hơn nhiều: heap không gian heap message. Nó xảy ra vì 2 ^ 31-1 int nguyên thủy bạn đang cố gắng để làm cho chỗ cho yêu cầu 8G bộ nhớ ít hơn so với mặc định được sử dụng bởi JVM này.

Ví dụ này cũng giải thích tại sao lỗi rất hiếm – để thấy giới hạn VM trên kích thước mảng bị đánh, bạn cần phải phân bổ một mảng với kích thước ngay giữa giới hạn nền tảng và Integer.MAX_INT. Khi ví dụ của chúng tôi được chạy trên 64bit Mac OS X với Hotspot 7, chỉ có hai độ dài mảng đó: Integer.MAX_INT-1 và Integer.MAX_INT.

Giải pháp cho vấn đề này là gì?

Java.lang.OutOfMemoryError: Yêu cầu kích thước mảng vượt quá giới hạn VM có thể xuất hiện như là kết quả của một trong các tình huống sau:
Mảng của bạn phát triển quá lớn và kết thúc có một kích thước giữa giới hạn nền tảng và Integer.MAX_INT
Bạn cố tình phân bổ mảng lớn hơn 2 ^ 31-1 để thử nghiệm các giới hạn.

Trong trường hợp thứ hai – nhớ rằng mảng Java được lập chỉ mục bởi int. Vì vậy, bạn không thể vượt quá 2 ^ 31-1 các yếu tố trong mảng của bạn khi sử dụng cấu trúc dữ liệu chuẩn trong nền tảng. Trong thực tế, trong trường hợp này bạn đã bị chặn bởi trình biên dịch thông báo “lỗi: số nguyên quá lớn” trong quá trình biên dịch.

Nhưng nếu bạn thực sự làm việc với bộ dữ liệu thật sự lớn, bạn cần phải suy nghĩ lại các lựa chọn của mình. Bạn có thể tải dữ liệu bạn cần để làm việc với các lô nhỏ hơn và vẫn sử dụng các công cụ Java tiêu chuẩn hoặc bạn có thể vượt xa các tiện ích tiêu chuẩn. Một cách để đạt được điều này là nhìn vào lớp sun.misc.Unsafe. Điều này cho phép bạn phân bổ bộ nhớ trực tiếp như bạn sẽ trong C.


(Bài viết trên được dịch từ bài báo Requested array size exceeds VM limit trên trang web Plumbr)

Table of Contents

Bài viết liên quan