上一篇文章我們講了JVM運行時數(shù)據(jù)區(qū)域與內(nèi)存溢出異常富岳,其中對于內(nèi)存溢出異常這部分將的不夠詳細蛔溃,這篇文章將著重講解Java內(nèi)存溢出異常的相關(guān)知識脏毯。如果有沒看過上一篇文章的小伙伴們滴某,請點擊Java內(nèi)存區(qū)域與內(nèi)存溢出異常乡翅。
Java的內(nèi)存溢出異常主要分為兩類:分別是內(nèi)存溢出和棧溢出剪个。在以下幾種情況秧骑,會拋出內(nèi)存異常:Java堆溢出、虛擬機棧和本地方法棧溢出扣囊、方法區(qū)和運行時常量池溢出乎折、以及本機直接內(nèi)存溢出,下面講一一介紹這幾類異常侵歇。
Java堆溢出
在Java內(nèi)存區(qū)域與內(nèi)存溢出異常中講過骂澄,Java堆主要是用來存儲對象實例的。這部分的內(nèi)存區(qū)域的大小可以通過-Xms參數(shù)和-Xmx參數(shù)進行設(shè)置惕虑,通常將-Xms和-Xmx的值設(shè)置為相同的值坟冲,以減少內(nèi)存擴展或者收縮時的開銷。
Java堆的空間是有限的枷遂,受到物理內(nèi)存與虛擬機內(nèi)存的雙重限制(通常虛擬機內(nèi)存的會設(shè)置成小于物理內(nèi)存)樱衷。因此,如果對象實例的數(shù)量不斷增加酒唉,而垃圾回收機制沒有進行及時清理的時候矩桂,對象實例所占用的空間就會達到Java堆的空間最大值。此時,就會因為Java堆內(nèi)存不足侄榴,導(dǎo)致無法為新的實例分配空間雹锣,從而拋出OutOfMemoryError異常。
通過設(shè)置-Xms20m -Xmx20m運行以下代碼可以模擬這一情況:
/**
* VM Args: -Xms20m -Xmx20m
*
* @author bdq
*/
public class HeapOOM {
static class OOMObject {
}
public static void main(String[] args) {
List<OOMObject> objects = new ArrayList<>();
while (true) {
objects.add(new OOMObject());
}
}
}
運行結(jié)果:
java.lang.OutOfMemoryError: Java heap space
這即是常見的OOM異常癞蚕,針對這類異常蕊爵,往往在打印異常信息的同時會進一步提示異常原因,如上圖所示的"Java heap space"桦山。當然攒射,只靠這點信息不足以判斷到底是內(nèi)存容量設(shè)置小了,還是出現(xiàn)了內(nèi)存泄漏(關(guān)于內(nèi)存泄漏的知識將會在后面的文章中進行講述)恒水。因此我們還要輔以其他手段來進一步確定問題的根源会放,比如加上-XX:+HeapDumpOnOutOfMemoryError參數(shù)使得虛擬機在出現(xiàn)內(nèi)存溢出異常時Dump出當前的內(nèi)存堆轉(zhuǎn)儲快照,然后用相關(guān)的工具進行分析钉凌。這類知識咧最,本篇文章暫不作過多的講解,將會在后面的文章一一介紹御雕。
虛擬機棧和本地方法棧溢出
為什么要把虛擬機棧和本地方法棧的溢出放在一起討論呢矢沿,因為在HotSpot虛擬機中并不區(qū)分虛擬機棧和本地方法棧。對于HotSpot來說酸纲,雖然說-Xoss參數(shù)是用來設(shè)置本地方法棧大小捣鲸,但實際上是無效的,棧的容量只由-Xss參數(shù)設(shè)定福青。
在Java虛擬機規(guī)范中對虛擬機棧和本地方法棧描述了兩種異常:
- 如果線程請求的棧深度大于虛擬機所允許的最大深度摄狱,將拋出StackOverflowError異常。
- 如果虛擬機在擴展棧時無法申請到足夠的內(nèi)存空間无午,則拋出OutOfMemoryError異常媒役。
這種分類其實并不是很明確,因為內(nèi)存太小或者已使用的椣艹伲空間太大都會導(dǎo)致椇ㄖ裕空間無法繼續(xù)分配。
StackOverflowError的出現(xiàn)條件很簡單次泽,下面這段簡單的代碼就會出現(xiàn)棧溢出:
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) {
JavaVMStackSOF javaVMStackSOF = new JavaVMStackSOF();
try {
javaVMStackSOF.stackLeak();
} catch (Throwable e) {
System.out.println("Stack length:" + javaVMStackSOF.stackLength);
throw e;
}
}
}
運行結(jié)果如下:
Stack length:18663
Exception in thread "main" java.lang.StackOverflowError
at cn.bdqfork.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
at cn.bdqfork.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
at cn.bdqfork.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
at cn.bdqfork.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
......
對上面的運行結(jié)果穿仪,不同的計算機Stack length的大小是不確定的,從輸出的異常信息來看意荤,是因為stackLeak方法遞歸調(diào)用層數(shù)過多導(dǎo)致的啊片。在大多數(shù)情況下,棧深度在虛擬機默認參數(shù)下是夠用的玖像。
OutOfMemoryError異常比較難以出現(xiàn)紫谷,一般發(fā)生在多線程環(huán)境下。當創(chuàng)建一個線程時,虛擬機會分配一個私有的楏宰颍空間給相應(yīng)的線程祖驱,這個空間的大小可以用-Xss參數(shù)來設(shè)置。通過不斷的創(chuàng)建新的進程瞒窒,可以產(chǎn)生內(nèi)存溢出異常捺僻。
原因是這樣的,當進程運行時崇裁,操作系統(tǒng)分配給進程的內(nèi)存是有限的匕坯,Java堆和方法區(qū)這兩部分占了大部分,忽略到程序計數(shù)器所占用的很小的一塊內(nèi)存寇壳,不計算虛擬機本身占用的內(nèi)存醒颖,剩下的就由虛擬機棧和本地方法棧所占用妻怎。因此壳炎,創(chuàng)建的線程數(shù)量到達一定程度時,虛擬機棧和本地方法棧所占用的空間就會使得進程的內(nèi)存空間不夠用逼侦,從而拋出內(nèi)存溢出異常匿辩。
這部分的測試代碼如下:
public class JavaVMStackOOM {
private void dontStop() {
while (true) {
}
}
public void stackLeakByThread() {
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args) {
JavaVMStackOOM javaVMStackOOM = new JavaVMStackOOM();
javaVMStackOOM.stackLeakByThread();
}
}
這段代碼的運行有一定的風(fēng)險,因為Java的線程并不是完全的用戶級線程榛丢,有映射到操作系統(tǒng)的部分铲球,所以可能會產(chǎn)生系統(tǒng)假死的現(xiàn)象,請謹慎運行晰赞。
運行結(jié)果如下:
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
由此可以看出稼病,我們在進行多線程開發(fā)時,對于線程的數(shù)量要有一定的把握掖鱼,線程池的復(fù)用是很有必要的然走。
受限于篇幅原因,剩余的知識點戏挡,將會在下一篇進行講解芍瑞。