JVM內(nèi)存溢出OOM

JVM中各個(gè)區(qū)域內(nèi)存都是有限的界酒,在內(nèi)存不足的情況下,繼續(xù)分配新的內(nèi)存空間嘴秸,而不對(duì)老的內(nèi)存空間進(jìn)行回收釋放毁欣,測(cè)試就會(huì)產(chǎn)生內(nèi)存溢出庇谆,即大名鼎鼎的OOM(Out Of Memory).

1. 產(chǎn)生OOM的區(qū)域


在JVM的五大區(qū)域(堆、Java虛擬機(jī)棧凭疮、本地方法棧饭耳、Metaspace、程序計(jì)數(shù)器)中执解,獨(dú)有程序計(jì)數(shù)器不會(huì)發(fā)生OOM寞肖,其他區(qū)域都會(huì)產(chǎn)生OOM,這是由于程序計(jì)數(shù)器中存儲(chǔ)的數(shù)據(jù)所占空間的大小不會(huì)隨程序的執(zhí)行而發(fā)生改變衰腌。所以新蟆,會(huì)導(dǎo)致OOM產(chǎn)生的區(qū)域是:

  • 堆,存放新創(chuàng)建的對(duì)象
  • Java虛擬機(jī)棧右蕊,存放棧幀琼稻、方法局部變量。
  • 本地方法棧
  • Metaspace饶囚, 主要用來(lái)存放類的字節(jié)碼信息

2. 解決OOM的思路

首先欣簇,需要確認(rèn)一點(diǎn)的是配置沒(méi)有劍走偏鋒,不存在某個(gè)區(qū)域給的內(nèi)存太小坯约,正常情況下就容易產(chǎn)生OOM熊咽。這點(diǎn)很容易做到,先看下服務(wù)器的JVM配置闹丐,然后再和推薦的配置進(jìn)行比較横殴。
服務(wù)器上的JVM配置,可以通過(guò)jinfo -flags 進(jìn)程號(hào)來(lái)查看卿拴,具體可以參考:JVM參數(shù)簡(jiǎn)介
衫仑。而推薦的配置可以通過(guò)PerfMa社區(qū)提供的XXFox產(chǎn)品來(lái)獲取,XXFox有個(gè)根據(jù)機(jī)器配置一鍵生成JVM參數(shù)的功能堕花,示意圖如下:

如果JVM的內(nèi)存配置看起來(lái)是合理的文狱,那我們就需要定位到底是哪個(gè)對(duì)象生成太多導(dǎo)致了OOM。而想要知道哪個(gè)對(duì)象太多缘挽,我們就要獲取發(fā)生OOM時(shí)的內(nèi)存快照文件(即dump文件)瞄崇,只需要在JVM的啟動(dòng)參數(shù)中加入以下參數(shù):

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/usr/local/oom

-XX:+HeapDumpOnOutOfMemoryError表示在發(fā)生OOM的時(shí)候自動(dòng)將內(nèi)存快照dump出來(lái), -XX:HeapDumpPath=/usr/local/oom參數(shù)指定dump文件存儲(chǔ)的路徑壕曼。

有了內(nèi)存快照之后苏研,我們就可以通過(guò)MAT之類的工具進(jìn)行分析。關(guān)于MAT的使用可以參考:JVM堆內(nèi)存分析工具-MAT

3. Java虛擬機(jī)棧的OOM


每個(gè)線程運(yùn)行時(shí)腮郊,都會(huì)創(chuàng)建對(duì)應(yīng)的虛擬機(jī)棧摹蘑,線程中運(yùn)行一個(gè)方法會(huì)創(chuàng)建一個(gè)棧幀,并將該棧幀入棧轧飞,方法執(zhí)行結(jié)束則棧幀出棧衅鹿。
一個(gè)線程的虛擬機(jī)棧內(nèi)存大小一般設(shè)置成1M, 假如程序不停的創(chuàng)建棧幀并入棧(即發(fā)生方法調(diào)用)撒踪,那么就有可能出現(xiàn)內(nèi)存溢出的情況。一般而言大渤,當(dāng)發(fā)生遞歸調(diào)用糠涛,容易產(chǎn)生虛擬機(jī)棧的OOM。 以下程序模擬產(chǎn)生虛擬機(jī)棧的OOM

/**
 * vm args:
 *    -XX:ThreadStackSize=1m
 * @author zhangguicong
 * @date 2019-12-23
 */
public class StackOomSample {

    private static long counter = 0 ;

    public static void main(String[] args) {
        call();
    }

    private static void call () {
        counter++;
        System.out.println("第"+counter+"次調(diào)用call方法");
        call();
    }

}

解決辦法

對(duì)線程的棧內(nèi)存和棧幀來(lái)說(shuō)兼犯,它們是不存在GC,所以沒(méi)發(fā)通過(guò)查看GC日志來(lái)定位此類問(wèn)題集漾。另外切黔,內(nèi)存快照對(duì)此也無(wú)法提供幫助。
一般而言具篇,只要把所有的異常都寫(xiě)入本地日志文件中纬霞,那么當(dāng)系統(tǒng)發(fā)生問(wèn)題時(shí)我們直接去看日志文件中的異常信息就可以。棧內(nèi)存溢出的報(bào)錯(cuò)日志是:

Exception in thread "main" java.lang.StackOverflowError

4. Metaspace的OOM


Metaspace即JDK1.8之前常說(shuō)的方法區(qū)驱显,JVM運(yùn)行過(guò)程中會(huì)不停地往這個(gè)區(qū)域加載類诗芜。當(dāng)Metaspace區(qū)域快要滿了,會(huì)觸發(fā)一次Full GC埃疫, 而在每次Full GC的時(shí)候也會(huì)嘗試回收Metaspace區(qū)域伏恐。Metaspace中class對(duì)象想要被回收,需要滿足以下條件:

  1. 所有由該class創(chuàng)建出來(lái)的對(duì)象都已經(jīng)被回收栓霜;
  2. class對(duì)象沒(méi)有引用執(zhí)行翠桦;
  3. 這個(gè)類的類加載器要先被回收。

因?yàn)橛辛艘陨蠗l件的限制胳蛮,所有class對(duì)象一般很難被回收掉销凑。

Metaspace一般極少發(fā)生OOM,如果真的發(fā)生了仅炊,請(qǐng)考慮以下兩種誘因斗幼。

  • Metaspace內(nèi)存空間設(shè)置很小。一般不進(jìn)行配置的話抚垄,默認(rèn)只有幾十M蜕窿,對(duì)于大型項(xiàng)目,可能要加載的類很多呆馁,所以可能會(huì)不夠渠羞,一般而言,可以設(shè)置成512M智哀;
  • 系統(tǒng)中使用動(dòng)態(tài)類加載技術(shù)(如次询,cglib)不當(dāng),導(dǎo)致動(dòng)態(tài)生產(chǎn)的類過(guò)多瓷叫。以下代碼模擬產(chǎn)生Metaspace的OOM
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * VM args:
 *    -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m  -XX:+PrintGCDetails -Xloggc:gc.log  -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./
 * @author zhangguicong
 * @date 2019-12-23
 */
public class MetaspaceOomSample {

    public static void main(String[] args) {
        dynamicCreateClass();
    }

    public static void dynamicCreateClass () {
        while(true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Dog.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    if (method.getName().equals("eat")) {
                        System.out.println("play with dog before dog eat.");
                    }
                    return methodProxy.invokeSuper(o,objects);
                }
            });

            Dog dog = ((Dog) enhancer.create());
            dog.eat();
        }
    }

    static class Dog {
        public void eat () {
            System.out.println("dog is eating bone");
        }
    }
}

5. 堆的OOM


對(duì)于虛擬機(jī)棧和Metaspace區(qū)域來(lái)說(shuō)屯吊,一般只要代碼上注意一些送巡,不太容易引發(fā)這兩塊區(qū)域的OOM。日常開(kāi)發(fā)中盒卸,更常見(jiàn)的是堆內(nèi)存中的OOM骗爆。

我們知道在堆內(nèi)存中,對(duì)象首先存放在新生代中蔽介,發(fā)生YoungGC之后可能進(jìn)入老年代摘投,老年內(nèi)存不足時(shí)再發(fā)生Full GC,但如果Full GC之后虹蓄,發(fā)現(xiàn)對(duì)象依然需要存活犀呼,無(wú)法將其內(nèi)存釋放掉,此時(shí)老年代無(wú)法再放入新的對(duì)象薇组,JVM只能選擇內(nèi)存溢出外臂。

發(fā)生堆內(nèi)存溢出的場(chǎng)景:

  1. 高并發(fā),系統(tǒng)承載請(qǐng)求量過(guò)大律胀,導(dǎo)致大量對(duì)象都是存活的宋光,無(wú)法放入新的對(duì)象;
  2. 系統(tǒng)存在內(nèi)存泄漏的問(wèn)題炭菌,莫名其妙弄了很多的對(duì)象罪佳,沒(méi)有及時(shí)取消對(duì)他們的引用,結(jié)果對(duì)象都是存活的黑低,導(dǎo)致觸發(fā)GC無(wú)法回收菇民。

以下代碼模擬產(chǎn)生堆的OOM

/**
 * vm args:
 *    -Xms10m -Xmx10m -XX:+PrintGCDetails -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./ -XX:+UseParNewGC -XX:+UseConcMarkSweepGC
 * @author zhangguicong
 * @date 2019-12-23
 */
public class HeapOomSample {

    public static void main(String[] args) {
        List<Person> personList = new ArrayList<>();
        while (true) {
            personList.add(new Person("zs",23));
        }
    }

    static class Person {
        private String name;
        private Integer age;

        public Person(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
    }
}

MAT分析dump文件

我們運(yùn)行上面的示例代碼,不一會(huì)生成dump文件投储,使用MAT打開(kāi)這個(gè)dump文件第练,可以看到如下圖所示:

點(diǎn)擊Leak Suspects,MAT給我們推測(cè)出來(lái)發(fā)生OOM的原因只有一個(gè):

我們看高亮部分的英文提示:main線程通過(guò)局部變量引用了96.98%的對(duì)象玛荞,且都被java.lang.Object[]的一個(gè)實(shí)例對(duì)象引用著娇掏。此時(shí)我們并不能知道Object[]中是什么東西,需要點(diǎn)擊 Details 》勋眯,點(diǎn)擊之后婴梧,我們可以看到:

到這里,我們可以知道OOM產(chǎn)生的原因是堆內(nèi)存中產(chǎn)生了大量的Person對(duì)象導(dǎo)致的客蹋。

此外我們還可以點(diǎn)擊“See stacktrace”查看線程的執(zhí)行棧情況塞蹭,定位到可能發(fā)生OOM的代碼所在位置:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市讶坯,隨后出現(xiàn)的幾起案子番电,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漱办,死亡現(xiàn)場(chǎng)離奇詭異这刷,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)娩井,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)暇屋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人洞辣,你說(shuō)我怎么就攤上這事咐刨。” “怎么了扬霜?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵定鸟,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我畜挥,道長(zhǎng),這世上最難降的妖魔是什么婴谱? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任蟹但,我火速辦了婚禮,結(jié)果婚禮上谭羔,老公的妹妹穿的比我還像新娘华糖。我一直安慰自己,他們只是感情好瘟裸,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布客叉。 她就那樣靜靜地躺著,像睡著了一般话告。 火紅的嫁衣襯著肌膚如雪兼搏。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天沙郭,我揣著相機(jī)與錄音佛呻,去河邊找鬼。 笑死病线,一個(gè)胖子當(dāng)著我的面吹牛吓著,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播送挑,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼绑莺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了惕耕?” 一聲冷哼從身側(cè)響起纺裁,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎司澎,沒(méi)想到半個(gè)月后对扶,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體区赵,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年浪南,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了笼才。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡络凿,死狀恐怖骡送,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情絮记,我是刑警寧澤摔踱,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站怨愤,受9級(jí)特大地震影響派敷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜撰洗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一篮愉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧差导,春花似錦试躏、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至助析,卻和暖如春犀被,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背外冀。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工弱判, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锥惋。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓昌腰,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親膀跌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子遭商,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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