Java內(nèi)存溢出異常(上)

上一篇文章我們講了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ī)范中對虛擬機棧和本地方法棧描述了兩種異常:

  1. 如果線程請求的棧深度大于虛擬機所允許的最大深度摄狱,將拋出StackOverflowError異常。
  2. 如果虛擬機在擴展棧時無法申請到足夠的內(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ù)用是很有必要的然走。

受限于篇幅原因,剩余的知識點戏挡,將會在下一篇進行講解芍瑞。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市褐墅,隨后出現(xiàn)的幾起案子拆檬,更是在濱河造成了極大的恐慌,老刑警劉巖妥凳,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件竟贯,死亡現(xiàn)場離奇詭異,居然都是意外死亡逝钥,警方通過查閱死者的電腦和手機屑那,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人齐莲,你說我怎么就攤上這事痢站。” “怎么了选酗?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵阵难,是天一觀的道長。 經(jīng)常有香客問我芒填,道長呜叫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任殿衰,我火速辦了婚禮朱庆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闷祥。我一直安慰自己娱颊,他們只是感情好,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布凯砍。 她就那樣靜靜地躺著箱硕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悟衩。 梳的紋絲不亂的頭發(fā)上剧罩,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天,我揣著相機與錄音座泳,去河邊找鬼惠昔。 笑死,一個胖子當著我的面吹牛挑势,可吹牛的內(nèi)容都是我干的镇防。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼薛耻,長吁一口氣:“原來是場噩夢啊……” “哼营罢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起饼齿,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤饲漾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后缕溉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體考传,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年证鸥,在試婚紗的時候發(fā)現(xiàn)自己被綠了僚楞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勤晚。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖泉褐,靈堂內(nèi)的尸體忽然破棺而出赐写,到底是詐尸還是另有隱情,我是刑警寧澤膜赃,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布挺邀,位于F島的核電站,受9級特大地震影響跳座,放射性物質(zhì)發(fā)生泄漏端铛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一疲眷、第九天 我趴在偏房一處隱蔽的房頂上張望禾蚕。 院中可真熱鬧,春花似錦狂丝、人聲如沸换淆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽产舞。三九已至,卻和暖如春菠剩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背耻煤。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工具壮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哈蝇。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓棺妓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親炮赦。 傳聞我的和親對象是個殘疾皇子怜跑,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容