1. Java heap space

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), mM(MB), kK(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分析某財險承保系統內存泄漏問題》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章