JVM運行時數(shù)據(jù)區(qū)域及異常實戰(zhàn)

聲明: 《深入理解Java虛擬機 JVM高級特性與最佳實踐 第2版》。以下內(nèi)容來自書中第二章硼莽。

1. JVM概述

JVM是Java Virtual Machine(Java虛擬機)的縮寫球散,JVM是一種用于計算設(shè)備的規(guī)范绕辖,它是一個虛構(gòu)出來的計算機颊乘,是通過在實際的計算機上仿真模擬各種計算機功能來實現(xiàn)的歧蒋。
引入Java語言虛擬機后,Java語言在不同平臺上運行時不需要重新編譯偿曙。Java語言使用Java虛擬機屏蔽了與具體平臺相關(guān)的信息氮凝,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節(jié)碼),就可以在多種平臺上不加修改地運行???????????? ——百度百科

可以將JVM理解成一臺機器望忆,這個機器可以用來執(zhí)行java程序罩阵。這樣java代碼就可以實現(xiàn)一次編寫竿秆,到處運行了。在不同的機器上配置相應(yīng)的JVM即可稿壁。 在機器上幽钢,JVM實際上也是一個程序。

2. JVM內(nèi)存分配

2.1 運行時數(shù)據(jù)區(qū)域

根據(jù)《Java 虛擬機規(guī)范(Java SE 7 版)》的規(guī)定傅是,Java虛擬機所有管理的內(nèi)存將會包括以下幾個運行時數(shù)據(jù)區(qū)域匪燕,如下圖所示:

javaRunTimeDataArea.png

簡單說一下各個數(shù)據(jù)區(qū)域的作用:

線程私有的數(shù)據(jù)區(qū)域:線程私有區(qū)域都是與線程同生共死的,即生命周期與線程相同喧笔。
線程共享的數(shù)據(jù)區(qū)域:生命周期與JVM相同

程序計數(shù)器:每個線程都有的"小本本"帽驯,線程不是連續(xù)不斷工作的。再次運行時它就要看看“小本本”里的內(nèi)容才知道自己該從什么地方繼續(xù)做下去书闸。
在JVM中的多線程是通過輪流切換的方式執(zhí)行的尼变,在任何時刻一個CPU(相當于多核CPU的一個核)只能運行一個線程。如果某個進程的某個線程占用CPU很長的時間梗劫,那么其他的線程會一直等下去嗎?為了讓用戶不會有:”哇梳侨!我這個傻X計算機怎么這么卡膀韧蚯嫌!“的錯覺。CPU就給了每個線程分配了一個CPU時間丙躏,當線程的CPU時間用完之后,他就會把CPU的計算資源讓出來給其他線程晒旅,等到下次輪到它的時候它再執(zhí)行。所以每個線程都需要一個私有的程序計數(shù)器來記錄自己執(zhí)行到哪兒了废恋。它實際可以理解成一個記錄程序執(zhí)行的字節(jié)碼的行號的指示器。 計數(shù)器只會占用內(nèi)存中很小的一部分空間鱼鼓。

Java虛擬機棧:程序員口中常說的“堆椖馓蹋”大抵說的就是這個區(qū)域中的局部變量表。虛擬機棧是用來描述Java方法執(zhí)行時的內(nèi)存模型迄本。這種描述是通過存儲方法開始執(zhí)行(入棧)到方法結(jié)束(出棧)過程中的局部變量表硕淑、操作數(shù)棧、動態(tài)鏈接及方法的出口等信息來是實現(xiàn)的。方法在執(zhí)行時會創(chuàng)建一個棧幀置媳,用來存儲前面說的各種信息于樟,方法完成,棧幀出棧拇囊。
局部變量表存放了編譯期間可知的各種基本數(shù)據(jù)類型和引用類型隔披,以前上課的時候說的堆棧的時候,老師可能會畫這樣的一張圖:

Variable Value
variable1 13
variable2 2.3
variable3 'a'
variable4 true
zhangsan(Student的一個實例) 指向堆中張三實例的地址空間

說的就是這個局部變量表寂拆。局部變量表所需要的內(nèi)存空間在編譯期間就分配完成。
以上可以對棧的作用做個小小的結(jié)論:

  1. 只有在方法調(diào)用時抓韩,才為當前棧分配一個幀纠永,然后將該幀壓入棧。
  2. 幀中存放了方法的局部變量表谒拴,當方法執(zhí)行完之后尝江,對應(yīng)的幀則從棧中彈出。

JVM規(guī)范中英上,對這個數(shù)據(jù)區(qū)域規(guī)定了兩種異常狀況:

  • StackOverflowError:線程請求的棧深度大于虛擬機所允許的深度
  • OutOfMemoryError:虛擬機棧動態(tài)擴展時無法申請都足夠的內(nèi)存空間

本地方法棧:與虛擬機棧相似炭序,不過虛擬機棧是為JVM執(zhí)行java方法,而本地方法棧則是為虛擬機使用到的Native方法服務(wù)苍日。
本地方法棧也可能會出現(xiàn)StackOverflowError和OutOfMemoryError惭聂。

Java堆:對大多數(shù)應(yīng)用來說,這個區(qū)域是JVM所管理的最大的內(nèi)存空間相恃,也是GC重點關(guān)注的區(qū)域辜纲。該區(qū)域被線程共享,存在的目的就是為了存放對象實例拦耐,幾乎所有的對象實例和數(shù)組都要在堆上分配耕腾。如果Java堆中沒有空間可以用來實例化對象,而且也沒法再申請新的內(nèi)存時杀糯,該區(qū)域會拋出OutOfMemoryError扫俺。

方法區(qū):線程共享區(qū)域,用于存儲已經(jīng)被虛擬機加載的類信息固翰、常量狼纬、靜態(tài)變量、即使編譯器后的代碼等數(shù)據(jù)倦挂。這個區(qū)域也有人稱其為永久代畸颅。方法區(qū)無法滿足內(nèi)存分配需求時也會拋出OutOfMemoryError。 運行時常量池也是方法區(qū)中的一部分方援,class文件會包括類的版本没炒,字段,方法送火,接口等描述信息,也會有個常量池弃衍,用于存放編譯期生成的各種字面量和符號引用。這部分內(nèi)容在類加載之后進入到方法區(qū)的運行時常量池中存放镜盯。該區(qū)域具有動態(tài)性速缆,即常量不一定是編譯期間就確定的恩闻,在運行期間也可以有新的常量產(chǎn)生,進入運行時常量池中破停。

3. 異常代碼實戰(zhàn)

實戰(zhàn)一下數(shù)據(jù)提供的代碼尉剩,旨在對運行時數(shù)據(jù)區(qū)域內(nèi)存分配和使用有更深的理解,當出現(xiàn)相關(guān)異常的時候能夠快速地定位到異常區(qū)域和異常代碼晤碘。

首先了解一下IDEA如何配置JVM啟動時的參數(shù)园爷。Run—>EditConfigurations

JVMConfiguration.png

JVM相關(guān)配置參數(shù)說明可以通過在CMD中通過java -X查看

JVMargs.png

Java 堆溢出

Java堆是用來存儲對象實例和數(shù)組的童社,只要的不停的創(chuàng)建對象扰楼,且確保對象不會被回收美浦,當對象的數(shù)量達到堆最大的容量限制后,就會產(chǎn)生內(nèi)存溢出異常了蹬竖。下面的JVM參數(shù)設(shè)置了Java堆內(nèi)存的大小為20MB,不可擴展(將最大值-Xms和最小值-Xmx設(shè)為一樣即可避免堆自動擴展)列另。通過參數(shù)-XX:+HeapDumpOnOutOfMemoryError可以讓虛擬機在出現(xiàn)內(nèi)存溢出異常的時候Dump出當前的內(nèi)存堆轉(zhuǎn)儲快照旦装,以便我們后面的分析。

JVM參數(shù)配置

-Xms20m 
-Xmx20m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=E:\\heapdump

代碼如下:

public class HeapOOM {
    static class OOMObject{}
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
    //不停地創(chuàng)建對象店乐,直到OOM
        while(true){
            list.add(new OOMObject());  
        }
    }
}

程序運行結(jié)果如下:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to E:\\heapdump\java_pid8948.hprof ...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3210)
    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 per.ling.JVMPractice.HeapOOM.main(HeapOOM.java:13)
Heap dump file created [28866091 bytes in 0.206 secs]

使用JDK自帶的內(nèi)存映像分析工具jvisualVM來分析程序dump出來的堆轉(zhuǎn)儲快照响巢。裝入文件后點擊類,我們可以看到類名和它對應(yīng)的實例個數(shù)及占用的內(nèi)存空間含长。

OOMprof.png

打開該文件可以看到OOMObject這個類有810326個實例拘泞,占用內(nèi)存13M左右。這里我們可以看到類實例相關(guān)的情況陪腌,查看概要我們還可以看到相關(guān)的線程及可能出現(xiàn)異常的代碼塊。

OOMprof3.png

如果是內(nèi)存泄露的話染簇,我們可能還需要觀察一下相關(guān)的GC Roots锻弓。
Java的內(nèi)存溢出有很多中情況蝌箍,剛興趣可以搜一下,學(xué)習(xí)一波杂拨。

虛擬機棧溢出

虛擬機椕醭模可能拋出的異常:

  • StackOverflowError:線程請求的棧深度大于虛擬機所允許的深度
  • OutOfMemoryError:虛擬機堆棧動態(tài)擴展時無法申請都足夠的內(nèi)存空間

JVM參數(shù)如下

-Xss128k
-XX:+HeapDumpOnStackOverflowErrow
-XX:HeapDumpPath=E:\\heapdump

代碼如下

public class StackOOM {

  private static long stackLength = 0L;

  public static void main(String[] args) {
    try {
      stackLeak();
    } catch (Throwable e){
      System.out.println("The length of statck is " + stackLength);
      throw e;
    }
  }

  private static void stackLeak() {
    stackLength++;
    stackLeak();
  }
}

控制臺輸出如下:

The length of statck is 41351
Exception in thread "main" java.lang.StackOverflowError
    at pers.leo.chapter_02.StackOOM.stackLeak(StackOOM.java:21)
    at pers.leo.chapter_02.StackOOM.stackLeak(StackOOM.java:21)
    at pers.leo.chapter_02.StackOOM.stackLeak(StackOOM.java:21)
    at pers.leo.chapter_02.StackOOM.stackLeak(StackOOM.java:21)
    at pers.leo.chapter_02.StackOOM.stackLeak(StackOOM.java:21)
    at pers.leo.chapter_02.StackOOM.stackLeak(StackOOM.java:21)
    at pers.leo.chapter_02.StackOOM.stackLeak(StackOOM.java:21)

小結(jié):
StackOverflowerError主要是針對運行時的棧而言贷币,而OutOfMemoryError(內(nèi)存溢出)針對的是整個內(nèi)存區(qū)域。后者出現(xiàn)的原因主要是申請內(nèi)存時沒有更多的內(nèi)存空間導(dǎo)致的偶摔,而導(dǎo)致這樣的原因有很多促脉。
詳情可以參見: Java常見的幾種內(nèi)存溢出及解決方案

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瘸味,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子藕夫,更是在濱河造成了極大的恐慌枯冈,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滩褥,死亡現(xiàn)場離奇詭異瑰煎,居然都是意外死亡俗孝,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進店門烘挫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柬甥,“玉大人,你說我怎么就攤上這事卤橄”弁猓” “怎么了喇颁?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵橘霎,是天一觀的道長姐叁。 經(jīng)常有香客問我洗显,道長,這世上最難降的妖魔是什么挠唆? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任玄组,我火速辦了婚禮,結(jié)果婚禮上嵌灰,老公的妹妹穿的比我還像新娘颅悉。我一直安慰自己剩瓶,他們只是感情好城丧,可當我...
    茶點故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布亡哄。 她就那樣靜靜地躺著,像睡著了一般蚊惯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上趴荸,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天发钝,我揣著相機與錄音,去河邊找鬼酝豪。 笑死,一個胖子當著我的面吹牛蒲障,可吹牛的內(nèi)容都是我干的夺英。 我是一名探鬼主播,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼余黎,長吁一口氣:“原來是場噩夢啊……” “哼载萌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起垮衷,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤搀突,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后仰迁,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顽分,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡卒蘸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年缸沃,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趾牧。...
    茶點故事閱讀 40,912評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡村缸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梯皿,到底是詐尸還是另有隱情,我是刑警寧澤东羹,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站权逗,受9級特大地震影響冤议,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜堪滨,卻給世界環(huán)境...
    茶點故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一蕊温、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧发笔,春花似錦凉翻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽韭赘。三九已至,卻和暖如春脉漏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背侧巨。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工司忱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人坦仍。 一個月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像幔荒,于是被迫代替她去往敵國和親梳玫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,922評論 2 361