1.1 java.lang.OutOfMemoryError: Java heap space 概述
Java 應(yīng)用只允許使用有限的內(nèi)存人断。這個(gè)限制是在應(yīng)用啟動(dòng)的時(shí)候指定的。展開(kāi)來(lái)說(shuō), Java內(nèi)存分成2個(gè)不同的區(qū)域。這兩個(gè)區(qū)域叫做Heap Space (堆內(nèi)存)和 Permgen (Permanent Generation部服,即永久代)。
[圖片上傳失敗...(image-f5ee75-1517724409969)]
這兩個(gè)區(qū)的大小是在JVM啟動(dòng)的時(shí)候設(shè)置, 可以通過(guò)JVM參數(shù)-Xmx
和 -XX:MaxPermSize
進(jìn)行設(shè)置. 如果你沒(méi)歐進(jìn)行特別的設(shè)置, 平臺(tái)指定的默認(rèn)配置會(huì)被使用.
java.lang.OutOfMemoryError: Java heap space
錯(cuò)誤會(huì)在應(yīng)用嘗試添加更多的數(shù)據(jù)到heap space, 但是heap區(qū)沒(méi)有足夠的空間時(shí)觸發(fā).
需要注意的是即使物理內(nèi)存可能有很多剩余, 但是只要JVM達(dá)到了heap size的限制, 就會(huì)拋出該錯(cuò)誤.
1.2 原因
對(duì)于 java.lang.OutOfMemoryError: Java heap space
, 最常見(jiàn)的原因很簡(jiǎn)單 -- 你把一個(gè)XXL號(hào)的應(yīng)用放到了一個(gè)S號(hào)的Java heap space里了. 也就是說(shuō) -- 應(yīng)用需要更多的Java heap space 來(lái)讓它正常運(yùn)行. 對(duì)于這個(gè)OutOfMemory, 其他的原因會(huì)更復(fù)雜, 通常是由于編程錯(cuò)誤引起的:
-
用戶/數(shù)據(jù)量出現(xiàn)峰值 該應(yīng)用被設(shè)計(jì)來(lái)處理一定數(shù)量的用戶和一定數(shù)量的數(shù)據(jù). 當(dāng)用戶數(shù)或數(shù)據(jù)量突然沖高, 并且超過(guò)了期望的閾值, 在出現(xiàn)峰值停止之前的正常運(yùn)行時(shí)的操作觸發(fā)了
java.lang.OutOfMemoryError: Java heap space
錯(cuò)誤. -
內(nèi)存泄漏 一種特定類型的編程錯(cuò)誤導(dǎo)致應(yīng)用頻繁消耗更多的內(nèi)存. 每當(dāng)應(yīng)用的泄漏的功能被使用時(shí), 它就會(huì)在Java heap space種生成一些對(duì)象. 隨著時(shí)間推移, 泄漏的對(duì)象消耗了所有可用的Java heap space, 并且觸發(fā)了常見(jiàn)的
java.lang.OutOfMemoryError: Java heap space
錯(cuò)誤.
1.3 示例
1.3.1 示例1
第一個(gè)例子相當(dāng)簡(jiǎn)單 -- 下列的Java 代碼嘗試分配200萬(wàn)個(gè)(2M) 整數(shù)數(shù)組. 當(dāng)你編譯該代碼, 用一個(gè)12MB大小的Java heap space (java -Xmx12m OOM
)運(yùn)行. 它會(huì)運(yùn)行失敗, 拋出 java.lang.OutOfMemoryError: Java heap space
消息. 有13MB Java heap space, 這個(gè)程序就能正常運(yùn)行...
class OOM {
static final int SIZE=2*1024*1024;
public static void main (String[] a) {
int[] i = new int[SIZE]
}
}
1.3.2 內(nèi)存泄漏示例
第二個(gè), 更現(xiàn)實(shí)一點(diǎn)的例子是內(nèi)存泄漏. 在Java里, 當(dāng)開(kāi)發(fā)創(chuàng)建和使用新對(duì)象, 如: new Integer(5)
, 他們不必自己分派內(nèi)存 -- 這通過(guò)JVM來(lái)處理. 在應(yīng)用生命周期種, JVM會(huì)周期性地檢查內(nèi)存中的哪個(gè)對(duì)象仍在使用, 哪個(gè)沒(méi)有. 沒(méi)有被使用的對(duì)象會(huì)被丟棄, 然后內(nèi)存重新聲明并重新使用. 這個(gè)過(guò)程叫做垃圾回收. 對(duì)應(yīng)的JVM里的模塊叫做垃圾收集器.
Java的自動(dòng)內(nèi)存管理機(jī)制以來(lái)與GC來(lái)周期性地查找沒(méi)用的對(duì)象并移除他們. 簡(jiǎn)而言之, Java內(nèi)存泄漏是這么一種場(chǎng)景, 一些對(duì)象應(yīng)用已經(jīng)不用了, 但是GC卻沒(méi)有檢查出來(lái). 結(jié)果就是這些沒(méi)用的對(duì)象仍然無(wú)限期地存在在Java heap space 中. 如此往復(fù), 最終觸發(fā)java.lang.OutOfMemoryError: Java heap space
錯(cuò)誤.
構(gòu)造一個(gè)滿足內(nèi)存泄漏定義的Java程序也相當(dāng)容易:
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);
}
}
當(dāng)執(zhí)行上面的代碼時(shí)拗慨,您可能期望它永遠(yuǎn)運(yùn)行而沒(méi)有任何問(wèn)題廓八,假設(shè)原始緩存解決方案只將Map擴(kuò)展到10,000個(gè)元素,除此之外赵抢,HashMap中已經(jīng)包含了所有鍵. 然而, 事實(shí)上元素會(huì)持續(xù)增加因?yàn)镵ey這個(gè)類沒(méi)有在它的hashCode()
種包含一個(gè)適當(dāng)?shù)?code>equals()實(shí)現(xiàn).
結(jié)果, 隨著時(shí)間推移, 因?yàn)樾孤┐a的不斷的使用, "緩存"的結(jié)果會(huì)消耗大量的Java heap space. 當(dāng)泄漏的內(nèi)存填滿了heap區(qū)的所有的可用內(nèi)存, 并且垃圾收集器無(wú)法清理, 會(huì)拋出java.lang.OutOfMemoryError: Java heap space
.
解決辦法也簡(jiǎn)單 -- 添加個(gè)equals()
方法的實(shí)現(xiàn)在下邊, 就能很好的運(yùn)行了. 但是在你最終找到這個(gè)bug之前, 你會(huì)西歐愛(ài)好相當(dāng)多的腦細(xì)胞.
@Override
public boolean equals(Object o) {
boolean response = false;
if (o instanceof Key) {
response = (((Key)o).id).equals(this.id);
}
return response;
}
1.4 解決方案
顯然第一個(gè)解決方案就是 -- 當(dāng)你的JVM特定資源耗盡了, 你應(yīng)該增加那個(gè)資源的量. 在這個(gè)案例種: 當(dāng)你的應(yīng)用沒(méi)有足夠的Java heap space內(nèi)存來(lái)正常運(yùn)行, 只需要在運(yùn)行JVM的時(shí)候配置并添加(或修改現(xiàn)有的)如下參數(shù):
-Xmx1024m
上述配置會(huì)給應(yīng)用1024M的Java heap space. 你可以使用g
或者G
(單位是GB), m
或M
(MB), k
或K
(KB). 例如下列都是設(shè)置最大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只是飲鴆止渴. 例如, 如果你的應(yīng)用存在內(nèi)存泄漏, 添加更多的heap只是延緩java.lang.OutOfMemoryError: Java heap space
錯(cuò)誤的出現(xiàn), 并不能解決問(wèn)題. 另外, 增加Java heap space也會(huì)導(dǎo)致GC暫停時(shí)間的增加, 從而影響你的應(yīng)用的吞吐量和延遲.
如果你希望解決潛在的問(wèn)題, 而不是頭痛醫(yī)頭, 聯(lián)系我就是最好的方式(@ ̄ー ̄@). 當(dāng)然, 有幾個(gè)工具適合你. Debuggers, profiles, heap dump analyzers -- 供你選擇.
題外話:
Dynatrace 也是個(gè)分析OOM問(wèn)題的好工具.感興趣的可以參考這篇文章:
《案例: Dynatrace分析某財(cái)險(xiǎn)承保系統(tǒng)內(nèi)存泄漏問(wèn)題》