java.lang.OutOfMemoryError錯誤:
5. Unable to create new native thread
7. Requested array size exceeds VM limit
1. Java heap space
原因分析
heap和permgen的最大內存大小, 由JVM啟動參數 -Xmx
和 -XX:MaxPermSize
指定. 如果沒有明確指定, 則根據平台類型(OS版本+ JVM版本)和物理內存的大小來確定。
假如在創建新的對象時, 堆內存中的空間不足以存放新創建的對象, 就會引發java.lang.OutOfMemoryError: Java heap space
錯誤。
不管機器上還沒有空閑的物理內存, 只要堆內存使用量達到最大內存限制,就會拋出 java.lang.OutOfMemoryError: Java heap space
錯誤。
產生 java.lang.OutOfMemoryError: Java heap space
錯誤的原因, 很多時候, 就類似於將 XXL 號的對象,往 S 號的 Java heap space 里面塞。其實清楚了原因, 就很容易解決對不對? 只要增加堆內存的大小, 程序就能正常運行. 另外還有一些比較復雜的情況, 主要是由代碼問題導致的:
- 超出預期的訪問量/數據量。 應用系統設計時,一般是有 “容量” 定義的, 部署這么多機器, 用來處理一定量的數據/業務。 如果訪問量突然飆升, 超過預期的閾值, 類似於時間坐標系中針尖形狀的圖譜, 那么在峰值所在的時間段, 程序很可能就會卡死、並觸發 java.lang.OutOfMemoryError: Java heap space 錯誤。
- 內存泄露(Memory leak). 這也是一種經常出現的情形。由於代碼中的某些錯誤, 導致系統占用的內存越來越多. 如果某個方法/某段代碼存在內存泄漏的, 每執行一次, 就會(有更多的垃圾對象)占用更多的內存. 隨着運行時間的推移, 泄漏的對象耗光了堆中的所有內存, 那么 java.lang.OutOfMemoryError: Java heap space 錯誤就爆發了。
解決方案
如果設置的最大內存不滿足程序的正常運行, 只需要增大堆內存即可, -Xmx1024m
2. GC overhead limit exceeded
原因分析
JVM拋出 java.lang.OutOfMemoryError: GC overhead limit exceeded 錯誤就是發出了這樣的信號: 執行垃圾收集的時間比例太大, 有效的運算量太小. 默認情況下, 如果GC花費的時間超過 98%, 並且GC回收的內存少於 2%, JVM就會拋出這個錯誤。
注意, java.lang.OutOfMemoryError: GC overhead limit exceeded 錯誤只在連續多次 GC 都只回收了不到2%的極端情況下才會拋出。假如不拋出 GC overhead limit
錯誤會發生什么情況呢? 那就是GC清理的這么點內存很快會再次填滿, 迫使GC再次執行. 這樣就形成惡性循環, CPU使用率一直是100%, 而GC卻沒有任何成果. 系統用戶就會看到系統卡死 - 以前只需要幾毫秒的操作, 現在需要好幾分鍾才能完成。
解決方案
有一種應付了事的解決方案, 就是不想拋出 “java.lang.OutOfMemoryError: GC overhead limit exceeded” 錯誤信息, 則添加下面啟動參數: -XX:-UseGCOverheadLimit
3. GC overhead limit exceeded
原因分析
在JDK1.7及之前的版本, 永久代(permanent generation) 主要用於存儲加載/緩存到內存中的 class 定義, 包括 class 的 名稱(name), 字段(fields), 方法(methods)和字節碼(method bytecode); 以及常量池(constant pool information); 對象數組(object arrays)/類型數組(type arrays)所關聯的 class, 還有 JIT 編譯器優化后的class信息等。
很容易看出, PermGen 的使用量和JVM加載到內存中的 class 數量/大小有關。可以說 java.lang.OutOfMemoryError: PermGen space 的主要原因, 是加載到內存中的 class 數量太多或體積太大。
解決方案
1. 解決程序啟動時產生的 OutOfMemoryError
在程序啟動時, 如果 PermGen 耗盡而產生 OutOfMemoryError 錯誤, 那很容易解決. 增加 PermGen 的大小, 讓程序擁有更多的內存來加載 class 即可. 修改 -XX:MaxPermSize 啟動參數, 類似下面這樣: java -XX:MaxPermSize=512m com.yourcompany.YourClass
2. 解決 redeploy 時產生的 OutOfMemoryError
我們可以進行堆轉儲分析(heap dump analysis) —— 在 redeploy 之后, 執行堆轉儲, 類似下面這樣: jmap -dump:format=b,file=dump.hprof <process-id>
3. 解決運行時產生的 OutOfMemoryError
如果在運行的過程中發生 OutOfMemoryError, 首先需要確認 GC是否能從PermGen中卸載class。 官方的JVM在這方面是相當的保守(在加載class之后,就一直讓其駐留在內存中,即使這個類不再被使用). 但是, 現代的應用程序在運行過程中, 會動態創建大量的class, 而這些class的生命周期基本上都很短暫, 舊版本的JVM 不能很好地處理這些問題。那么我們就需要允許JVM卸載class。使用下面的啟動參數: -XX:+CMSClassUnloadingEnabled
4. Metaspace
原因分析
從Java 8開始,內存結構發生重大改變, 不再使用Permgen, 而是引入一個新的空間: Metaspace. 這種改變基於多方面的考慮, 部分原因列舉如下:
- Permgen空間的具體多大很難預測。指定小了會造成 java.lang.OutOfMemoryError: Permgen size 錯誤, 設置多了又造成浪費。
- 為了 GC 性能 的提升, 使得垃圾收集過程中的並發階段不再 停頓, 另外對 metadata 進行特定的遍歷(specific iterators)。
- 對 G1垃圾收集器 的並發 class unloading 進行深度優化。
在Java8中,將之前 PermGen 中的所有內容, 都移到了 Metaspace 空間。例如: class 名稱, 字段, 方法, 字節碼, 常量池, JIT優化代碼, 等等。
Metaspace 的使用量與JVM加載到內存中的 class 數量/大小有關。可以說, java.lang.OutOfMemoryError: Metaspace 錯誤的主要原因, 是加載到內存中的 class 數量太多或者體積太大。
解決方案
如果拋出與 Metaspace 有關的 OutOfMemoryError , 第一解決方案是增加 Metaspace 的大小. 使用下面這樣的啟動參數: -XX:MaxMetaspaceSize=512m
5. Unable to create new native thread
原因分析
JVM向操作系統申請創建新的 native thread(原生線程)時, 就有可能會碰到 java.lang.OutOfMemoryError: Unable to create new native thread 錯誤. 如果底層操作系統創建新的 native thread 失敗, JVM就會拋出相應的OutOfMemoryError. 原生線程的數量受到具體環境的限制, 通過一些測試用例可以找出這些限制, 請參考下文的示例. 但總體來說, 導致 java.lang.OutOfMemoryError: Unable to create new native thread 錯誤的場景大多經歷以下這些階段:
- Java程序向JVM請求創建一個新的Java線程;
- JVM本地代碼(native code)代理該請求, 嘗試創建一個操作系統級別的 native thread(原生線程);
- 操作系統嘗試創建一個新的native thread, 需要同時分配一些內存給該線程;
- 如果操作系統的虛擬內存已耗盡, 或者是受到32位進程的地址空間限制(約2-4GB), OS就會拒絕本地內存分配;
- JVM拋出 java.lang.OutOfMemoryError: Unable to create new native thread 錯誤。
解決方案
有時可以修改系統限制來避開 Unable to create new native thread 問題. 假如JVM受到用戶空間(user space)文件數量的限制, 像下面這樣,就應該想辦法增大這個值:
[root@dev ~]# ulimit -a core file size (blocks, -c) 0 ...... 省略部分內容 ...... max user processes (-u) 1800
6. Out of swap space
原因分析
如果 native heap 內存耗盡, 內存分配時, JVM 就會拋出 java.lang.OutOfmemoryError: Out of swap space? 錯誤消息, 這個消息告訴用戶, 請求分配內存的操作失敗了。
Java進程使用了虛擬內存才會發生這個錯誤。 對 Java的垃圾收集 來說這是很難應付的場景。即使現代的 GC算法 很先進, 但虛擬內存交換引發的系統延遲, 會讓 GC暫停時間 膨脹到令人難以容忍的地步。
通常是操作系統層面的原因導致 java.lang.OutOfMemoryError: Out of swap space? 問題, 例如:
- 操作系統的交換空間太小。
- 機器上的某個進程耗光了所有的內存資源。
當然也可能是應用程序的本地內存泄漏(native leak)引起的, 例如, 某個程序/庫不斷地申請本地內存,卻不進行釋放。
解決方案
第一種, 也是最簡單的方法, 增加虛擬內存(swap space) 的大小. 各操作系統的設置方法不太一樣, 比如Linux,可以使用下面的命令設置:
swapoff -a dd if=/dev/zero of=swapfile bs=1024 count=655360 mkswap swapfile swapon swapfile
7. 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
錯誤的原因可能是:
- 數組太大, 最終長度超過平台限制值, 但小於
Integer.MAX_INT
- 為了測試系統限制, 故意分配長度大於
2^31-1
的數組。
第一種情況, 需要檢查業務代碼, 確認是否真的需要那么大的數組。如果可以減小數組長度, 那就萬事大吉. 如果不行,可能需要把數據拆分為多個塊, 然后根據需要按批次加載。
如果是第二種情況, 請記住, Java 數組用 int 值作為索引。所以數組元素不能超過 2^31-1
個. 實際上, 代碼在編譯階段就會報錯,提示信息為 “error: integer number too large
”。
8. nativeGetNewTLA
weblogic服務器內存溢出,經常報 java.lang.OutOfMemoryError: nativeGetNewTLA。 經查是由於 weblogic 使用 jrockit jvm時才會出現這樣的問題。網上查了好久解決方案,給出的都是 調整 -XXtlaSize 這個參數。按照網上的說法,我將 -XXtlaSize 調成512M依然出錯。
郁悶之余,直接改weblogic的jvm, 改完之后就沒有再報錯了。
修改方法:在startWeblgoic.sh 頭上加一句 JAVA_VENDOR=Sun 即可。