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

此篇是上一篇文章Java內(nèi)存溢出異常(上)的續(xù)篇,沒有看過的同學(xué)栈雳,可以先看一下上篇丘逸。本篇文章將介紹剩余的兩個溢出異常:方法區(qū)和運行時常量池溢出次屠。

方法區(qū)和運行時常量池溢出

這部分為什么會放在一起呢媒楼?在Java內(nèi)存區(qū)域與內(nèi)存溢出異常這篇文章中我們說過乐尊,運行時常量池實際上屬于方法區(qū)的一部分,所以就放在一起討論匣砖。

常量池溢出

在討論常量池的溢出之前科吭,先說明一下String.intern()方法昏滴,該方法會檢查字符串常量池中是否已經(jīng)包含了一個等于此String對象的字符串猴鲫,如果已經(jīng)包含了,則返回代表池中這個字符串的String對象谣殊;否則拂共,將此String對象包含的字符串添加到常量池中,并返回此String對象的引用姻几∫撕看過fastjson源代碼的同學(xué)應(yīng)該知道势告,該方法在fastjson這個json解析庫中大量的出現(xiàn),以提高json的解析速度抚恒。

在JDK 1.6之前的版本中咱台,常量池是分配在永久代的,可以通過-XX:PermSize和-XX:MaxPermSize參數(shù)來設(shè)置大小俭驮,從而間接限制其中常量池的容量回溺。

通過以上條件,我們可以輕易的復(fù)現(xiàn)這個異常混萝,代碼如下:

public class RuntimeConstantPoolOOM {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        int i = 0;
        while (true) {
            list.add(String.valueOf(i++).intern());
        }
    }
}

如果你的JDK環(huán)境是1.6版本之前的遗遵,你會得到如下運行結(jié)果:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
    at java.lang.String.intern(Native Method)
    ......

如果你是JDK 1.7+,那么這段代碼會無限的運行下去逸嘀。因為在JDK 1.7之后對String.intern()方法進行了修改车要。

繼續(xù)看下面這段經(jīng)典的代碼:

public class RuntimeConstantPoolOOM {

    public static void main(String[] args) {
        String str1 = new StringBuilder("計算機").append("軟件").toString();
        System.out.println(str1.intern() == str1);

        String str2 = new StringBuilder("ja").append("va").toString();
        System.out.println(str2.intern() == str2);
    }
}

這段代碼在JDK 1.6中運行,會得到兩個false崭倘,而在JDK 1.7中運行翼岁,會得到一個true和一個false。

出現(xiàn)差異的原因是因為司光,在JDK 1.6中登澜,intern()方法會把首次遇到的字符串實例復(fù)制到常量池中,返回的也是常量池中這個字符串實例的引用飘庄,而由StringBuilder創(chuàng)建的字符串實例在Java堆上脑蠕,所以必然不是同一個引用,將返回false跪削。而JDK 1.7中的intern()實訓(xùn)不會再復(fù)制實例谴仙,只是在常量池中記錄首次出現(xiàn)的實例引用,因此intern()返回的引用和由StringBuilder創(chuàng)建的那個字符串實例是同一個碾盐。對str2比較返回false是因為“java”這個字符串在執(zhí)行StringBuilder.toString()之前已經(jīng)出現(xiàn)過了晃跺,字符串中常量池中已經(jīng)有它的引用了,不符合“首次”出現(xiàn)的原則毫玖。

這就可以解釋掀虎,為什么在JDK 1.7+版本之后不能用String.intern()方法使常量池溢出的原因了,intern()不會像JDK 1.6之前的版本一樣無限復(fù)制實例到常量池中了付枫。

方法區(qū)溢出

方法區(qū)用于存放Class的相關(guān)信息烹玉,如類名、訪問修飾符阐滩、常量池二打、字段描述、方法描述等掂榔。這部分的測試可以通過生成大量的類去填滿方法區(qū)继效,直到溢出症杏,可以借助CGLib直接操作字節(jié)碼運行時生成大量的動態(tài)類,代碼如下:

public class JavaMethodAreaOOM {

    public static void main(final String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invokeSuper(o, objects);
                }
            });
            enhancer.create();
        }
    }

    static class OOMObject {

    }
}

運行結(jié)果如下:

Caused by: java.lang.OutOfMemoryError: PermGen space
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
    ...

在經(jīng)常動態(tài)生成大量Class的應(yīng)用中瑞信,比如說使用CGLib這類字節(jié)碼技術(shù)的時候厉颤,容易出現(xiàn)方法區(qū)溢出。所以說在使用這類技術(shù)編寫框架時凡简,要留意這方面導(dǎo)致的內(nèi)存溢出走芋。

本機直接內(nèi)存溢出

本機直接內(nèi)存的溢出主要與大量的直接操作內(nèi)存的API有關(guān),比如說NIO相關(guān)的API潘鲫,也可以通過rt.jar中的類使用Unsafe的功能來復(fù)現(xiàn)這個異常翁逞。DirectMemory容量可通過-XX:MaxDirectMemorySize指定,主要代碼如下:

public class DirectMemoryOOM {

    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) throws Exception {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true) {
            unsafe.allocateMemory(_1MB);
        }
    }
}

運行結(jié)果如下:

Exception in thread "main" java.lang.OutOfMemoryError
    at sun.misc.Unsafe.allocateMemory(Native Method)
    ...

由DirectMemory導(dǎo)致的內(nèi)存溢出溉仑,不會在Heap Dump文件中不會看見明顯的異常挖函,如果發(fā)現(xiàn)OOM之后Dump文件很小,而程序中又直接或間接使用了NIO浊竟,那就可以考慮檢查一下是不是這方面的原因怨喘。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市振定,隨后出現(xiàn)的幾起案子必怜,更是在濱河造成了極大的恐慌,老刑警劉巖后频,帶你破解...
    沈念sama閱讀 223,207評論 6 521
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梳庆,死亡現(xiàn)場離奇詭異,居然都是意外死亡卑惜,警方通過查閱死者的電腦和手機膏执,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,455評論 3 400
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來露久,“玉大人更米,你說我怎么就攤上這事『梁郏” “怎么了征峦?”我有些...
    開封第一講書人閱讀 170,031評論 0 366
  • 文/不壞的土叔 我叫張陵,是天一觀的道長消请。 經(jīng)常有香客問我栏笆,道長,這世上最難降的妖魔是什么梯啤? 我笑而不...
    開封第一講書人閱讀 60,334評論 1 300
  • 正文 為了忘掉前任竖伯,我火速辦了婚禮存哲,結(jié)果婚禮上因宇,老公的妹妹穿的比我還像新娘七婴。我一直安慰自己,他們只是感情好察滑,可當(dāng)我...
    茶點故事閱讀 69,322評論 6 398
  • 文/花漫 我一把揭開白布打厘。 她就那樣靜靜地躺著,像睡著了一般贺辰。 火紅的嫁衣襯著肌膚如雪户盯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,895評論 1 314
  • 那天饲化,我揣著相機與錄音莽鸭,去河邊找鬼。 笑死吃靠,一個胖子當(dāng)著我的面吹牛硫眨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播巢块,決...
    沈念sama閱讀 41,300評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼礁阁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了族奢?” 一聲冷哼從身側(cè)響起姥闭,我...
    開封第一講書人閱讀 40,264評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎越走,沒想到半個月后棚品,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,784評論 1 321
  • 正文 獨居荒郊野嶺守林人離奇死亡廊敌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,870評論 3 343
  • 正文 我和宋清朗相戀三年南片,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庭敦。...
    茶點故事閱讀 40,989評論 1 354
  • 序言:一個原本活蹦亂跳的男人離奇死亡疼进,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出秧廉,到底是詐尸還是另有隱情伞广,我是刑警寧澤,帶...
    沈念sama閱讀 36,649評論 5 351
  • 正文 年R本政府宣布疼电,位于F島的核電站嚼锄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蔽豺。R本人自食惡果不足惜区丑,卻給世界環(huán)境...
    茶點故事閱讀 42,331評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沧侥,春花似錦可霎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,814評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至旺罢,卻和暖如春旷余,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背扁达。 一陣腳步聲響...
    開封第一講書人閱讀 33,940評論 1 275
  • 我被黑心中介騙來泰國打工正卧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人跪解。 一個月前我還...
    沈念sama閱讀 49,452評論 3 379
  • 正文 我出身青樓穗酥,卻偏偏與公主長得像,于是被迫代替她去往敵國和親惠遏。 傳聞我的和親對象是個殘疾皇子砾跃,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,995評論 2 361

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