1.1 java.lang.OutOfMemoryError: Java heap space 概述
Java 應用只允許使用有限的內存。這個限制是在應用啓動的時候指定的。展開來說, Java內存分成2個不同的區域。這兩個區域叫做Heap Space (堆內存)和 Permgen (Permanent Generation,即永久代)。
這兩個區的大小是在JVM啓動的時候設置, 可以通過JVM參數-Xmx
和 -XX:MaxPermSize
進行設置. 如果你沒歐進行特別的設置, 平臺指定的默認配置會被使用.
java.lang.OutOfMemoryError: Java heap space
錯誤會在應用嘗試添加更多的數據到heap space, 但是heap區沒有足夠的空間時觸發.
需要注意的是即使物理內存可能有很多剩餘, 但是隻要JVM達到了heap size的限制, 就會拋出該錯誤.
1.2 原因
對於 java.lang.OutOfMemoryError: Java heap space
, 最常見的原因很簡單 – 你把一個XXL號的應用放到了一個S號的Java heap space裏了. 也就是說 – 應用需要更多的Java heap space 來讓它正常運行. 對於這個OutOfMemory, 其他的原因會更復雜, 通常是由於編程錯誤引起的:
- 用戶/數據量出現峯值 該應用被設計來處理一定數量的用戶和一定數量的數據. 當用戶數或數據量突然衝高, 並且超過了期望的閾值, 在出現峯值停止之前的正常運行時的操作觸發了
java.lang.OutOfMemoryError: Java heap space
錯誤. - 內存泄漏 一種特定類型的編程錯誤導致應用頻繁消耗更多的內存. 每當應用的泄漏的功能被使用時, 它就會在Java heap space種生成一些對象. 隨着時間推移, 泄漏的對象消耗了所有可用的Java heap space, 並且觸發了常見的
java.lang.OutOfMemoryError: Java heap space
錯誤.
1.3 示例
1.3.1 示例1
第一個例子相當簡單 – 下列的Java 代碼嘗試分配200萬個(2M) 整數數組. 當你編譯該代碼, 用一個12MB大小的Java heap space (java -Xmx12m OOM
)運行. 它會運行失敗, 拋出 java.lang.OutOfMemoryError: Java heap space
消息. 有13MB Java heap space, 這個程序就能正常運行…
class OOM {
static final int SIZE=2*1024*1024;
public static void main (String[] a) {
int[] i = new int[SIZE]
}
}
1.3.2 內存泄漏示例
第二個, 更現實一點的例子是內存泄漏. 在Java裏, 當開發創建和使用新對象, 如: new Integer(5)
, 他們不必自己分派內存 – 這通過JVM來處理. 在應用生命週期種, JVM會週期性地檢查內存中的哪個對象仍在使用, 哪個沒有. 沒有被使用的對象會被丟棄, 然後內存重新聲明並重新使用. 這個過程叫做垃圾回收. 對應的JVM裏的模塊叫做垃圾收集器.
Java的自動內存管理機制以來與GC來週期性地查找沒用的對象並移除他們. 簡而言之, Java內存泄漏是這麼一種場景, 一些對象應用已經不用了, 但是GC卻沒有檢查出來. 結果就是這些沒用的對象仍然無限期地存在在Java heap space 中. 如此往復, 最終觸發java.lang.OutOfMemoryError: Java heap space
錯誤.
構造一個滿足內存泄漏定義的Java程序也相當容易:
class KeylessEntry {
static class Key {
Integer id;
Key(Integer id) {
this.id = id;
}
@Override
public int hashCode() {
return id.hashCode();
}
}
public static void main(String[] args) {
Map m = new HashMap();
while (true)
for (int i=0; i<10000, i++)
if (!m.containsKey(new Key(i)))
m.put(new Key(i), "Nmber:" + i);
}
}
當執行上面的代碼時,您可能期望它永遠運行而沒有任何問題,假設原始緩存解決方案只將Map擴展到10,000個元素,除此之外,HashMap中已經包含了所有鍵. 然而, 事實上元素會持續增加因爲Key這個類沒有在它的hashCode()
種包含一個適當的equals()
實現.
結果, 隨着時間推移, 因爲泄漏代碼的不斷的使用, “緩存”的結果會消耗大量的Java heap space. 當泄漏的內存填滿了heap區的所有的可用內存, 並且垃圾收集器無法清理, 會拋出java.lang.OutOfMemoryError: Java heap space
.
解決辦法也簡單 – 添加個equals()
方法的實現在下邊, 就能很好的運行了. 但是在你最終找到這個bug之前, 你會西歐愛好相當多的腦細胞.
@Override
public boolean equals(Object o) {
boolean response = false;
if (o instanceof Key) {
response = (((Key)o).id).equals(this.id);
}
return response;
}
1.4 解決方案
顯然第一個解決方案就是 – 當你的JVM特定資源耗盡了, 你應該增加那個資源的量. 在這個案例種: 當你的應用沒有足夠的Java heap space內存來正常運行, 只需要在運行JVM的時候配置並添加(或修改現有的)如下參數:
-Xmx1024m
上述配置會給應用1024M的Java heap space. 你可以使用g
或者G
(單位是GB), m
或M
(MB), k
或K
(KB). 例如下列都是設置最大Java heap space爲1GB:
java -Xmx1073741824 com.mycompany.MyClass
java -Xmx1048576k com.mycompany.MyClass
java -Xmx1024m com.mycompany.MyClass
java -Xmx1g com.mycompany.MyClass
然而, 在很多案例種, 提供更多的Java heap space只是飲鴆止渴. 例如, 如果你的應用存在內存泄漏, 添加更多的heap只是延緩java.lang.OutOfMemoryError: Java heap space
錯誤的出現, 並不能解決問題. 另外, 增加Java heap space也會導致GC暫停時間的增加, 從而影響你的應用的吞吐量和延遲.
如果你希望解決潛在的問題, 而不是頭痛醫頭, 聯繫我就是最好的方式(@ ̄ー ̄@). 當然, 有幾個工具適合你. Debuggers, profiles, heap dump analyzers – 供你選擇.
題外話:
Dynatrace 也是個分析OOM問題的好工具.感興趣的可以參考這篇文章:
《案例: Dynatrace分析某財險承保系統內存泄漏問題》