JVM之內(nèi)存模型以及各種溢出異常

近期學(xué)習(xí)了JVM,借此整理一下JVM有關(guān)的內(nèi)存模型和各種內(nèi)存溢出泼差。

運(yùn)行時(shí)數(shù)據(jù)區(qū)域

要理解Java的內(nèi)存模型椭盏,作者覺得最好是從線程的角度去理解比較好。分為線程共享部分和線程隔離部分。這樣各個(gè)區(qū)域有各自的用途利职,以及分配和清除的時(shí)間趣效。有些區(qū)域隨著用戶線程產(chǎn)生而產(chǎn)生,有些區(qū)域隨著虛擬機(jī)啟動(dòng)的時(shí)候就開始存在猪贪。Java程序運(yùn)行時(shí)的數(shù)據(jù)區(qū)域主要如下所示(注意:Java SE 1.7)


Java虛擬機(jī)運(yùn)行時(shí)的數(shù)據(jù)區(qū)

程序計(jì)數(shù)器

程序計(jì)數(shù)器是一塊比較小的內(nèi)存跷敬,可以看做是當(dāng)前線程的執(zhí)行的字節(jié)碼的行號(hào)指示器。因?yàn)镴ava程序的class文件是字節(jié)碼热押,虛擬機(jī)通過程序計(jì)數(shù)器來執(zhí)行下一條需要執(zhí)行的指令(循環(huán)西傀,跳轉(zhuǎn),異常處理桶癣,線程恢復(fù)等)拥褂。這其中有一個(gè)特別重要的功能:Java的多線程中,是通過爭奪cpu來執(zhí)行程序的牙寞,沒有爭奪到cpu的線程則會(huì)進(jìn)入等待的狀態(tài)饺鹃,而當(dāng)?shù)却木€程搶占到cpu后,會(huì)有狀態(tài)的切換碎税。這時(shí)候則根據(jù)程序計(jì)數(shù)器的指令來恢復(fù)到正確執(zhí)行的位置尤慰。還有一個(gè)重點(diǎn)就是,這個(gè)區(qū)域是Java虛擬機(jī)規(guī)范中唯一一個(gè)不會(huì)出現(xiàn)OutOfmemoryError的區(qū)域雷蹂。

虛擬機(jī)棧

每一個(gè)線程創(chuàng)建的時(shí)候同時(shí)會(huì)創(chuàng)建自己獨(dú)立的虛擬機(jī)棧伟端,用于存放棧幀,棧幀是用于存放局部變量和一些過程結(jié)果的地方匪煌。Java虛擬機(jī)規(guī)范規(guī)定虛擬機(jī)椩痱穑可以固定分配或者動(dòng)態(tài)擴(kuò)展。
虛擬機(jī)椢ィ可能發(fā)生如下異常情況:

  • 如果線程請(qǐng)求分配的棧容量大于虛擬機(jī)允許的容量(也就是滿棧)的時(shí)候霜医,虛擬機(jī)就會(huì)拋出StackOverflowError異常。
  • 如果線程新建時(shí)沒有足夠的內(nèi)存去創(chuàng)建虛擬機(jī)棧驳规,或者在平時(shí)動(dòng)態(tài)擴(kuò)展過程中肴敛,已經(jīng)申請(qǐng)擴(kuò)展,但無法申請(qǐng)到足夠的內(nèi)存去擴(kuò)展虛擬機(jī)棧吗购,那虛擬機(jī)就會(huì)拋出一個(gè)OutOfMemoryError異常医男。
  /** 
 *    虛擬機(jī)棧StackOverflowError示例                                   
 * VM args:-Xss128k                        
 */                                        
public class StackSOF{                     
    public static void main(String[] args){
        neverGoOut();                      
    }                                      
                                           
    public static void neverGoOut(){       
        neverGoOut();                      
    }                                      
}                                          
/**
* 虛擬機(jī)棧OutOfMemoryError示例
* 在Linux物理機(jī)上才能跑出來
* VM args: -Xss2M
*/
public class JavaVMStackOOM{
    public static void main(String[] args){
        while(true){
            new Thread(new Runnable(){
                @Override
                public void run(){
                    neverDown();
                }
            }).start();
        }
    }

    private static void neverDown(){
        while(true){}
    }
}

本地方法棧

和虛擬機(jī)棧一樣,每一個(gè)線程創(chuàng)建的時(shí)候也會(huì)創(chuàng)建自己獨(dú)立的本地方法棧捻勉,只不過這個(gè)棧是用來存放本地方法的镀梭,也就是native調(diào)用的方法。本地方法棧也是可以固定分配或者動(dòng)態(tài)擴(kuò)展踱启。
本地方法棻ㄕ耍可能發(fā)生如下異常情況:

  • 如果線程請(qǐng)求分配的棧容量大于本地方法棧允許的容量的時(shí)候研底,虛擬機(jī)就會(huì)拋出一個(gè)StackOverflowError異常。
  • 如果線程新建時(shí)沒有足夠的內(nèi)存去創(chuàng)建本地方法棧透罢,或者在平時(shí)動(dòng)態(tài)擴(kuò)展過程中榜晦,已經(jīng)申請(qǐng)擴(kuò)展,但無法申請(qǐng)到足夠的內(nèi)存去擴(kuò)展虛擬機(jī)棧琐凭,那虛擬機(jī)就會(huì)拋出一個(gè)OutOfMemoryError異常芽隆。

雖然HotSpot有-Xoss參數(shù)可以設(shè)置本地方法棧的大小,但實(shí)際上是無效的统屈,棧容量只有-Xss參數(shù)設(shè)定,所以該部分的驗(yàn)證方法參考虛擬機(jī)棧胚吁。

方法區(qū)

在Java虛擬機(jī)中,方法區(qū)是線程運(yùn)行是共享的區(qū)域愁憔,它存儲(chǔ)著每一個(gè)類的結(jié)構(gòu)信息腕扶,包括運(yùn)行時(shí)常量池,字段和方法數(shù)據(jù)吨掌,構(gòu)造方法和普通方法的字節(jié)碼內(nèi)容半抱。因?yàn)榉椒▍^(qū)是線程共享的部分,所以它在Java虛擬機(jī)啟動(dòng)的時(shí)候被創(chuàng)建膜宋。
方法區(qū)可能發(fā)生如下異常情況:

  • 如果方法區(qū)的內(nèi)存空間不能滿足內(nèi)存分配的要求窿侈,Java虛擬機(jī)則會(huì)拋出一個(gè)OutOfMemoryError異常。
    驗(yàn)證:
import java.util.List;
import java.util.ArrayList;

/**
 * 方法區(qū)運(yùn)行常量池OutOfMemoryError示例
 * Java version:1.6因?yàn)?.6的String.intern()方法是在首次出現(xiàn)的字符串復(fù)制入永久代秋茫,而1.7版本則只會(huì)放置一個(gè)引用到永久代(所以不能觸發(fā)內(nèi)存溢出)
 * VM args: -XX:PermSize=10M -XX:MaxPermSize=10m
 * */
public class RuntimeConstantPoolOOM{

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

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
*方法區(qū)加載類信息OutOfMemoryError示例
* jar包:cglib-3.2.4.jar,asm-5.1.jar
* VM args:-XX:PermSize=10M -XX:MaxPermSize=10M
*/
public class JavaMethodAreaOOM{
    public static void main(String[] args){
        while(true){
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor(){
                @Override
                public Object intercept(Object obj, Method method, Object[] objs, MethodProxy proxy)throws Throwable{
                    return proxy.invokeSuper(obj, objs);
                }
            });
            enhancer.create();
        }
    }

    static class OOMObject{}
}

Java堆

堆是各個(gè)線程共享的運(yùn)行時(shí)內(nèi)存區(qū)域史简,也就是每一個(gè)對(duì)象和數(shù)組的分配內(nèi)存的區(qū)域。堆在Java虛擬機(jī)啟動(dòng)的時(shí)候就創(chuàng)建了肛著,它存儲(chǔ)了內(nèi)存自動(dòng)管理系統(tǒng)圆兵,也就是我們常說的垃圾回收器。堆是可以固定大小也可以動(dòng)態(tài)分配的枢贿。
堆可能發(fā)生如下異常情況:
如果實(shí)際所需的堆超過了垃圾回收器提供的最大容量殉农,Java虛擬機(jī)則會(huì)拋出一個(gè)OutOfMemoryError異常。
驗(yàn)證:

/**
* Java堆OutOfMemoryError示例
* VM args:-Xmx10M -Xms10M
*/
public class HeapOOM{

   public static final int _1MB = 1024 * 1024;

   public static void main(String[] args) {
       byte[] b = new byte[10 * 1024 * 1024];
   }
}

本機(jī)直接內(nèi)存

直接內(nèi)存分配可以越過堆直接向操作系統(tǒng)申請(qǐng)分配內(nèi)存局荚。DirectMemory容量可以通過-XX:MaxDirectMemorySize指定超凳,如果不指定,則默認(rèn)和Java堆最大值一樣耀态。以下示例代碼是用unsafe直接分配本機(jī)內(nèi)存導(dǎo)致的內(nèi)存溢出:

import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
* 直接內(nèi)存分配OutOfMemoryError示例
* VM args:-Xmx20M -XX:MaxDirectMemorySize=10M
*/
public class DirectMemoryOOM{
    private static final int _1MB = 1024 * 1024;
    public static void main(String[] args) throws Exception{
        Field field = Unsafe.class.getDeclaredFields()[0];
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe)field.get(null);
        while(true){
            unsafe.allocateMemory(_1MB);
        }
    }
}

參考資料

《深入理解Java虛擬機(jī)》
《Java 虛擬機(jī)規(guī)范(Java SE 7 版)》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末轮傍,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子茫陆,更是在濱河造成了極大的恐慌金麸,老刑警劉巖擎析,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件簿盅,死亡現(xiàn)場離奇詭異挥下,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)桨醋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門棚瘟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人喜最,你說我怎么就攤上這事偎蘸。” “怎么了瞬内?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵迷雪,是天一觀的道長。 經(jīng)常有香客問我虫蝶,道長章咧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任能真,我火速辦了婚禮赁严,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘粉铐。我一直安慰自己疼约,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布蝙泼。 她就那樣靜靜地躺著程剥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪踱承。 梳的紋絲不亂的頭發(fā)上倡缠,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音茎活,去河邊找鬼昙沦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛载荔,可吹牛的內(nèi)容都是我干的盾饮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼懒熙,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼丘损!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起工扎,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤徘钥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后肢娘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呈础,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡舆驶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了而钞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沙廉。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖臼节,靈堂內(nèi)的尸體忽然破棺而出撬陵,到底是詐尸還是另有隱情,我是刑警寧澤网缝,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布巨税,位于F島的核電站,受9級(jí)特大地震影響粉臊,放射性物質(zhì)發(fā)生泄漏垢夹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一维费、第九天 我趴在偏房一處隱蔽的房頂上張望果元。 院中可真熱鬧,春花似錦犀盟、人聲如沸而晒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽倡怎。三九已至,卻和暖如春贱枣,著一層夾襖步出監(jiān)牢的瞬間监署,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工纽哥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留钠乏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓春塌,卻偏偏與公主長得像晓避,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子只壳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

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