這是本系列的第七篇文章, 相關文章列表:
- OutOfMemoryError系列(1): Java heap space
- OutOfMemoryError系列(2): GC overhead limit exceeded
- OutOfMemoryError系列(3): Permgen space
- OutOfMemoryError系列(4): Metaspace
- OutOfMemoryError系列(5): Unable to create new native thread
- OutOfMemoryError系列(6): Out of swap space?
Java平臺限制了數組的最大長度。各個版本的具體限制可能稍有不同, 但範圍都在 1 ~ 21億
之間。
如果程序拋出 java.lang.OutOfMemoryError: Requested array size exceeds VM limit
錯誤, 就說明想要創建的數組長度超過限制。
原因分析
這個錯誤是由JVM中的本地代碼拋出的. 在真正爲數組分配內存之前, JVM會執行一項檢查: 要分配的數據結構在該平臺是否可以尋址(addressable). 當然, 這個錯誤比你所想的還要少見得多。
一般很少看到這個錯誤, 因爲Java使用 int 類型作爲數組的下標(index, 索引)。在Java中, int類型的最大值爲 2^31 – 1 = 2,147,483,647
。大多數平臺的限制都約等於這個值 —— 例如在 64位的 MB Pro 上, Java 1.7 平臺可以分配長度爲 2,147,483,645
, 以及 Integer.MAX_VALUE-2
) 的數組。
再增加一點點長度, 變成 Integer.MAX_VALUE-1
時, 就會拋出我們所熟知的 OutOfMemoryError
:
`Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit`
在有的平臺上, 這個最大限制可能還會更小一些, 例如在32位Linux, OpenJDK 6 上面, 數組長度大約在 11億左右(約2^30
) 就會拋出 “java.lang.OutOfMemoryError: Requested array size exceeds VM limit
“ 錯誤。要找出具體的限制值, 可以執行一個小小的測試用例, 具體示例參見下文。
示例
以下代碼用來演示 java.lang.OutOfMemoryError: Requested array size exceeds VM limit
錯誤:
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();
}
}
其中,for循環迭代4次, 每次都去初始化一個 int 數組, 長度從 Integer.MAX_VALUE-3
開始遞增, 到 Integer.MAX_VALUE
爲止. 在 64位 Mac OS X 的 Hotspot 7 平臺上, 執行這段代碼會得到類似下面這樣的結果:
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)
請注意, 在後兩次迭代拋出 java.lang.OutOfMemoryError: Requested array size exceeds VM limit
錯誤之前, 先拋出了2次 java.lang.OutOfMemoryError: Java heap space
錯誤。 這是因爲 2^31-1
個 int 數佔用的內存超過了JVM默認的8GB堆內存。
此示例也展示了這個錯誤比較罕見的原因 —— 要取得JVM對數組大小的限制, 要分配長度差不多等於 Integer.MAX_INT
的數組. 這個示例運行在64位的Mac OS X, Hotspot 7平臺時, 只有兩個長度會拋出這個錯誤: Integer.MAX_INT-1
和 Integer.MAX_INT
。
解決方案
發生 java.lang.OutOfMemoryError: Requested array size exceeds VM limit
錯誤的原因可能是:
- 數組太大, 最終長度超過平臺限制值, 但小於
Integer.MAX_INT
- 爲了測試系統限制, 故意分配長度大於
2^31-1
的數組。
第一種情況, 需要檢查業務代碼, 確認是否真的需要那麼大的數組。如果可以減小數組長度, 那就萬事大吉. 如果不行,可能需要把數據拆分爲多個塊, 然後根據需要按批次加載。
如果是第二種情況, 請記住, Java 數組用 int 值作爲索引。所以數組元素不能超過 2^31-1
個. 實際上, 代碼在編譯階段就會報錯,提示信息爲 “error: integer number too large
”。
如果確實需要處理超大數據集, 那就要考慮調整解決方案了. 例如拆分成多個小塊,按批次加載; 或者放棄使用標準庫,而是自己處理數據結構,比如使用 sun.misc.Unsafe
類, 通過Unsafe工具類可以像C語言一樣直接分配內存。
原文鏈接: https://plumbr.eu/outofmemoryerror/requested-array-size-exceeds-vm-limit
翻譯日期: 2017年9月21日