JVM之Java內(nèi)存區(qū)域與內(nèi)存溢出異常

Java的JVM可以自動管理內(nèi)存构挤,包括內(nèi)存動態(tài)分配和垃圾收集等髓介。

簡介

JVM在執(zhí)行Java程序的過程中會把它所管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域。這些區(qū)域都有各自的用途筋现,以及創(chuàng)建和銷毀的時(shí)間唐础,有的區(qū)域隨著JVM進(jìn)程的啟動而存在,有些區(qū)域則依賴用戶線程的啟動和結(jié)束而建立和銷毀矾飞。

先看看JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)包括哪幾個(gè)部分:

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

可以看出JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)包括:堆一膨、虛擬機(jī)棧、本地方法棧洒沦、方法區(qū)豹绪、程序計(jì)數(shù)器和運(yùn)行時(shí)常量池。其中申眼,運(yùn)行時(shí)常量池在方法區(qū)里瞒津。

接下來對這幾個(gè)區(qū)域一一介紹蝉衣。

Java堆

Java堆(Java Heap)是JVM所管理的內(nèi)存中最大的一塊。

堆的作用是用來存放對象實(shí)例巷蚪,所有的對象實(shí)例和數(shù)組都在堆上分配內(nèi)存病毡。

堆也是垃圾收集器管理的主要區(qū)域,因此也被稱為“GC堆”屁柏。因?yàn)槔占髦饕脕硎占瘜ο罄材ぃ瑢ο笤诙焉戏峙洌宰匀欢咽抢占鞴芾淼闹饕獏^(qū)域淌喻。

堆被所有的線程共享僧家,在虛擬機(jī)啟動時(shí)創(chuàng)建,物理上可在不連續(xù)的內(nèi)存空間中裸删,跟磁盤空間一樣八拱。

Java堆溢出

什么情況下會堆溢出

當(dāng)創(chuàng)建新對象,堆上內(nèi)存不夠時(shí)就會產(chǎn)生堆溢出烁落。

只要不斷創(chuàng)建對象乘粒,并且保證GC Roots到對象之間有可達(dá)路徑來避免垃圾回收機(jī)制清除這些對象,那么當(dāng)堆上的內(nèi)存不夠創(chuàng)建新對象時(shí)就會產(chǎn)生內(nèi)存溢出異常伤塌。

制造堆溢出

/**
 * -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 */
public class HeapOOM {
    static class OOMObject{

    }

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        long length=0;

        while (true){
            try {
                list.add(new OOMObject());
                length += 1;
            } catch (Throwable e){
                System.out.println("number of obj: "+length);
                throw e;
            }
        }
    }
}

輸出:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid39193.hprof ...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Heap dump file created [27917334 bytes in 0.227 secs]
    at java.util.Arrays.copyOf(Arrays.java:3210)
number of obj: 810325
    at java.util.Arrays.copyOf(Arrays.java:3181)
    at java.util.ArrayList.grow(ArrayList.java:261)
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
    at java.util.ArrayList.add(ArrayList.java:458)
    at com.lbd.jvm.HeapOOM.main(HeapOOM.java:20)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

Process finished with exit code 1

解決堆溢出

重點(diǎn)確認(rèn)內(nèi)存中的對象是否是必要的灯萍,也就是確認(rèn)到底是出現(xiàn)了內(nèi)存泄露(Memory Leak)還是內(nèi)存溢出(Memory Overflow)。

方法:通過內(nèi)存映像分析工具(如Eclipse Memory Analyzer)對Dump出來的堆轉(zhuǎn)儲快照進(jìn)行分析每聪。

如果是內(nèi)存泄露:

進(jìn)一步通過工具查看泄露對象到GC Roots的引用鏈旦棉,這樣就能找到泄露對象是通過怎樣的路徑與GC Roots相關(guān)聯(lián)并導(dǎo)致垃圾收集器無法自動回收它們的,這樣就可以比較準(zhǔn)確地定位出泄露代碼的位置

如果不是內(nèi)存泄露:

也就是內(nèi)存中的對象確實(shí)還必須存活這药薯,那就應(yīng)該檢查虛擬機(jī)的堆參數(shù)(-Xms和-Xmx)绑洛,看看是否可以調(diào)大一些。

方法區(qū)

方法區(qū)與Java堆一樣童本,是各個(gè)線程共享的內(nèi)存區(qū)域真屯,用于存儲已被虛擬機(jī)加載的類信息(類的版本、字段穷娱、方法绑蔫、接口等描述信息)、常量泵额、靜態(tài)變量配深、即使編譯器編譯后的代碼等數(shù)據(jù)。

方法區(qū)的內(nèi)存回收主要針對常量池的回收和堆類型的卸載嫁盲。垃圾收集行為在該區(qū)域比較少出現(xiàn)篓叶。

在HotSpot虛擬機(jī)中,方法區(qū)又被稱為“永久代”(Permanent Generation),這樣HotSpot的垃圾收集器可以像管理Java堆一樣管理這部分內(nèi)存缸托,就不用專門為方法區(qū)編寫內(nèi)存管理代碼了左敌。

方法區(qū)溢出

如果產(chǎn)生大量的類或者大量的字符串常量(運(yùn)行時(shí)常量池溢出)可能導(dǎo)致方法區(qū)溢出。

Java SE API可以動態(tài)產(chǎn)生類嗦董,如反射時(shí)的GeneratedConstructorAccessor和動態(tài)代理等母谎。

運(yùn)行時(shí)常量池

運(yùn)行時(shí)常量池是方法區(qū)的一部分,用于存放編譯期生成的各種字面量和符號引用京革,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放。

運(yùn)行時(shí)常量池溢出

什么時(shí)候會運(yùn)行時(shí)常量池溢出

產(chǎn)生大量的字符串常量幸斥。

制造運(yùn)行時(shí)常量池溢出

注:以下代碼只在JDK1.6及之前的版本才會產(chǎn)生運(yùn)行時(shí)常量池溢出異常匹摇,因?yàn)樵谶@些版本中常量池分配在永久代內(nèi),可以通過-XX:PermSize=1M -XX:MaxPermSize=1M來限制方法區(qū)的大小甲葬,從而間接限制其中常量池的容量廊勃。

/**
 * -XX:PermSize=1M -XX:MaxPermSize=1M
 */
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());
        }
    }
}

虛擬機(jī)棧

虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的時(shí)候都會創(chuàng)建一個(gè)棧幀(Stack Frame)用于存儲局部變量表、操作數(shù)棧经窖、動態(tài)鏈接坡垫、方法出口等信息。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過程画侣,就對應(yīng)者一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過程冰悠。

虛擬機(jī)棧是線程私有的。為Java方法服務(wù)配乱。

虛擬機(jī)棧中最重要的是局部變量表溉卓。局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型和對象引用類型。是在編譯期確定的搬泥。

虛擬機(jī)棧溢出

什么情況下會Java虛擬機(jī)棧溢出

  • 如果線程請求的棧深度大于虛擬機(jī)所允許的最大深度(一般是遞歸)桑寨,將拋出StackOverflowError異常。
  • 如果虛擬機(jī)在擴(kuò)展棧時(shí)無法申請到足夠的內(nèi)存空間忿檩,則拋出OutOfMemoryError異常尉尾。

制造虛擬機(jī)棧溢出(StackOverflowError)

遞歸。因?yàn)檫f歸需要用到棧燥透。設(shè)置棧容量為256k(-Xss256k)沙咏。

/**
 * -Xss256k
 */
public class JavaVMStackSOF {
    private int stackLength = 1;
    public void stackLeak(){
        stackLength++;
        stackLeak();
    }
    public static void main(String[] args) {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        } catch (Throwable e){
            System.out.println("stack length:" + oom.stackLength);
            throw e;
        }
    }
}

輸出:

Exception in thread "main" java.lang.StackOverflowError
stack length:2789
    at com.lbd.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
    at com.lbd.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
    at com.lbd.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
    at com.lbd.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
    at com.lbd.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)

當(dāng)調(diào)用了2789次后出現(xiàn)了棧溢出StackOverflowError。

制造虛擬機(jī)棧溢出(OutOfMemoryError)

創(chuàng)建足夠多的線程兽掰,當(dāng)擴(kuò)展棧時(shí)無法申請到足夠的內(nèi)存空間芭碍,就會拋出OutOfMemoryError異常。

/**
 * -Xss2M
 * dangerous,don't run this program!
 */
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 oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}

本地方法棧

本地方法棧與虛擬機(jī)棧所發(fā)揮的作用很相似孽尽。區(qū)別在于:虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù)窖壕,而本地方法棧為虛擬機(jī)使用到的Native方法服務(wù)。

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

程序計(jì)數(shù)器用來記錄正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址。程序計(jì)數(shù)器是線程私有的瞻讽。是唯一一個(gè)沒有規(guī)定任何OutOfMemoryError情況的區(qū)域鸳吸。

因?yàn)镴ava虛擬機(jī)的多線程是通過線程輪流切換并分配CPU執(zhí)行時(shí)間的方式來實(shí)現(xiàn)的。為了線程切換后能恢復(fù)到正確的執(zhí)行位置速勇,每個(gè)線程需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器晌砾,各線程之間計(jì)數(shù)器互不影響,獨(dú)立存儲烦磁。

直接內(nèi)存

直接內(nèi)存并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分养匈,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域。

JDK1.4中新加入了NIO(New Input/Ouput)類都伪,引入了一種基于通道(Channel)和緩沖區(qū)(Buffer)的I/O方式呕乎,它可以使用Native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個(gè)存儲在Java堆中的DirectByteBufer對象作為這塊內(nèi)存的引用進(jìn)行操作陨晶。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末猬仁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子先誉,更是在濱河造成了極大的恐慌湿刽,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件褐耳,死亡現(xiàn)場離奇詭異诈闺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)漱病,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門买雾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人杨帽,你說我怎么就攤上這事漓穿。” “怎么了注盈?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵晃危,是天一觀的道長。 經(jīng)常有香客問我老客,道長僚饭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任胧砰,我火速辦了婚禮鳍鸵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尉间。我一直安慰自己偿乖,他們只是感情好击罪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贪薪,像睡著了一般媳禁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上画切,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天竣稽,我揣著相機(jī)與錄音,去河邊找鬼霍弹。 笑死毫别,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的庞萍。 我是一名探鬼主播拧烦,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼钝计!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起齐佳,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤私恬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后炼吴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體本鸣,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年硅蹦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了荣德。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡童芹,死狀恐怖涮瞻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情假褪,我是刑警寧澤署咽,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站生音,受9級特大地震影響宁否,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜缀遍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一慕匠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧域醇,春花似錦台谊、人聲如沸蓉媳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽督怜。三九已至,卻和暖如春狠角,著一層夾襖步出監(jiān)牢的瞬間号杠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工丰歌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留姨蟋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓立帖,卻偏偏與公主長得像眼溶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子晓勇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

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