Java虛擬機(jī)基本結(jié)構(gòu)

Java虛擬機(jī)是JVM類語(yǔ)言的根基屯掖,其中動(dòng)態(tài)內(nèi)存管理和垃圾收集技術(shù)是JVM中最重要的特性性湿。本節(jié)主要講述其中的內(nèi)存管理相關(guān)概念纬傲。

一 Java虛擬機(jī)的基本結(jié)構(gòu)

Java虛擬機(jī)結(jié)構(gòu).jpg

如圖所示為Java虛擬機(jī)的基本結(jié)構(gòu),每個(gè)模塊介紹如下:

  • 類加載子系統(tǒng)
    類加載子系統(tǒng)負(fù)責(zé)從文件或網(wǎng)絡(luò)中加載Class字節(jié)碼信息肤频,然后存放于方法區(qū)叹括。
  • 方法區(qū)
    方法區(qū)是各個(gè)線程共享的內(nèi)存區(qū)域,用于存儲(chǔ)虛擬機(jī)加載的類變量宵荒、常量汁雷、靜態(tài)變量以及即時(shí)編譯后的代碼等數(shù)據(jù)。
  • Java堆
    在虛擬機(jī)啟動(dòng)的時(shí)候建立报咳,是Java程序最主要的內(nèi)存工作區(qū)域侠讯,幾乎所有的對(duì)象實(shí)例和數(shù)組都在Java堆上分配。和方法區(qū)一樣暑刃,是各個(gè)線程共享的內(nèi)存區(qū)域厢漩。可通過-Xmx和Xms虛擬機(jī)參數(shù)控制Java堆大小岩臣。
  • 垃圾回收系統(tǒng)
    垃圾回收是Java虛擬機(jī)的重要組成部分袁翁,其中的垃圾回收器可以對(duì)Java堆、方法區(qū)和直接內(nèi)存進(jìn)行回收婿脸。同時(shí)Java堆是回收器的工作重點(diǎn)粱胜。
  • 直接內(nèi)存
    在Java的NIO庫(kù)中,允許Java程序使用直接內(nèi)存狐树,它是Java堆外直接向系統(tǒng)申請(qǐng)的內(nèi)存區(qū)域焙压。通常情況下該區(qū)域的內(nèi)存訪問速度優(yōu)于Java堆。
  • Java棧
    Java棧是線程私有的,它的生命周期和線程相同涯曲。它在線程創(chuàng)建的時(shí)候被創(chuàng)建野哭。Java棧中保存幀信息,每個(gè)方法創(chuàng)建的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀幻件,用于存儲(chǔ)局部變量拨黔、方法參數(shù)、操作數(shù)棧绰沥、方法出口燈信息篱蝇,和方法的調(diào)用返回密切相關(guān)。
  • 本地方法棧
    和Java棧類似徽曲,但其中最大的不同是Java棧用于方法調(diào)用零截,而本地方法棧用于本地方法調(diào)用。
  • PC寄存器
    該區(qū)域也是每個(gè)線層私有的空間秃臣。Java虛擬會(huì)為每個(gè)Java線程創(chuàng)建PC寄存器涧衙。當(dāng)當(dāng)前執(zhí)行的方法不是本地方法時(shí),PC寄存器就會(huì)指向當(dāng)前正在被執(zhí)行的指令奥此。若當(dāng)前執(zhí)行的方式是本地方法弧哎,則PC寄存器的值為undefined。
  • 執(zhí)行引擎
    執(zhí)行引擎是虛擬機(jī)最核心的組件之一稚虎,它負(fù)責(zé)執(zhí)行虛擬機(jī)的字節(jié)碼撤嫩。

二 Java堆

Java堆是和Java應(yīng)用程序關(guān)系最為密切的內(nèi)存空間。Java堆內(nèi)存通過垃圾回收機(jī)制祥绞,垃圾對(duì)象會(huì)被自動(dòng)清理非洲,而不需要顯示的釋放鸭限。Java堆分為新生代和老年代蜕径。其中新生代存放新生對(duì)象或年齡不大的對(duì)象,而老年代則存放老年對(duì)象败京。新生代和老年代結(jié)構(gòu)如下圖所示:


Java堆結(jié)構(gòu).png

在大多數(shù)情況下兜喻,對(duì)象首先在Eden區(qū)分配,在一次新生代回收后赡麦,若對(duì)象還存活著則進(jìn)入S0或S1朴皆,在這之后,每經(jīng)一次新生代回收泛粹,若對(duì)象還存活著則它的年齡會(huì)加1遂铡,達(dá)到一定年齡后該對(duì)象就被認(rèn)為是老年對(duì)象,從而進(jìn)入老年代晶姊。當(dāng)然這里只是其中一種方式進(jìn)入老年代扒接,后續(xù)文章會(huì)有詳細(xì)敘述。

三 Java棧

Java棧是一塊線程私有內(nèi)存空間。Java棧用于傳遞每次函數(shù)調(diào)用的數(shù)據(jù)钾怔。它是一塊后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)碱呼,在其中保存的主要內(nèi)容是棧幀。每一次函數(shù)調(diào)用都會(huì)有對(duì)應(yīng)的棧幀壓如Java棧宗侦,同時(shí)函數(shù)結(jié)束時(shí)棧幀被彈出愚臀。


Java棧幀.jpg

上圖可用下面的代碼表示:

public void function1() {
    public void function2();
}

public void function2() {
    public void function3();
}
 public void function3() {
    ....
}
...

在一個(gè)棧幀中至少包含局部變量表、操作數(shù)棧和幀數(shù)據(jù)區(qū)幾個(gè)部分矾利。
但是Java椆昧眩空間也不能無(wú)限使用下去,它受-Xss參數(shù)限制梦皮,該參數(shù)也決定了函數(shù)調(diào)用的最大深度炭分。
示例:

public class TestStackDeep {
    private static int count = 0;
    public static void recursion() {
        count++;
        recursion();
    }
    public static void main(String[] args) {
        try {
            recursion();
        } catch (Throwable e) {
            System.out.println("deep of calling = " + count);
            e.printStackTrace();
        }
    }
}

上面的代碼計(jì)算最大棧深度,設(shè)置虛擬機(jī)參數(shù)-Xss256K剑肯,其結(jié)果為:

deep of calling = 2374

當(dāng)設(shè)置-Xss512K時(shí)捧毛,結(jié)果為:

deep of calling = 9245

棧溢出則會(huì)拋出java.lang.StackOverflowError異常。

1)局部變量表
局部變量表用于保存函數(shù)的參數(shù)以及局部變量让网,它同樣也隨函數(shù)的調(diào)用而生滅呀忧,函數(shù)變量表可通過jclasslib工具查看,在Idea中溃睹,jclasslib可作為插件方式安裝而账。
示例代碼:

public class TestStack {

    public void test1() {
        int m, n, i, j, k;
        System.out.println("hello world");
    }

    public void test2(int param1, int param2) {
        long m, n, i, j, k;
        System.out.println("hello world");
    }


    public static void main(String[] args) {
        TestStack testStack = new TestStack();

    }
}

通過查看jclasslib可看到以下內(nèi)容:


testStack

在jclasslib中,可看到當(dāng)前類中的靜態(tài)池因篇,接口字段以及方法等信息泞辐。查看方法的Code部分可看到局部變量表統(tǒng)計(jì)信息:


局部變量表信息

從上圖看出,test2()最大局部變量表占用大小為13字竞滓,因?yàn)閠est2()參數(shù)為兩個(gè)int咐吼,加上this字段以及5個(gè)long變量正好是13字(int占用一個(gè)字,this占用一個(gè)字商佑,long占用兩個(gè)字)锯茄。查看局部變量表信息可通過LocalVariableTable:


局部變量表

上圖中分別對(duì)應(yīng)了局部變量的作用域范圍,所在槽位(index)茶没,變量名(name)以及數(shù)據(jù)類型(Descriptor)
棧幀中局部變量表的槽位是可以復(fù)用的肌幽,如果一個(gè)局部變量過了其作用域,那么在其后申明的新局部變量就有可能復(fù)用過期局部變量的槽位抓半,從而達(dá)到節(jié)省資源的目的喂急。
2)操作數(shù)棧
操作數(shù)棧主要用于保存計(jì)算過程中間結(jié)果。也作為計(jì)算過程中變量的臨時(shí)存儲(chǔ)空間笛求。
3)幀數(shù)據(jù)區(qū)

4)棧上分配
棧上分配是Java虛擬機(jī)提供的一項(xiàng)優(yōu)化技術(shù)廊移,基本思想是對(duì)于線程私有對(duì)象(即不會(huì)被其它線程訪問到的對(duì)象實(shí)例)讥蔽,可以將它們分配在棧上,而不必從堆中分配画机,這樣的好處是該對(duì)象在函數(shù)調(diào)用完畢后可以自行銷毀而不必接入垃圾回收器冶伞,從而提高系統(tǒng)的整體性能。棧上分配的一個(gè)基礎(chǔ)技術(shù)是逃逸分析步氏,逃逸分析的目的是判斷對(duì)象作用域是否逃逸出函數(shù)體响禽。

在編譯程序優(yōu)化理論中,逃逸分析是一種確定指針動(dòng)態(tài)范圍的方法——分析在程序的哪些地方可以訪問到指針荚醒。它涉及到指針分析和形狀分析芋类。
當(dāng)一個(gè)變量(或?qū)ο?在子程序中被分配時(shí),一個(gè)指向變量的指針可能逃逸到其它執(zhí)行線程中界阁,或是返回到調(diào)用者子程序侯繁。如果使用尾遞歸優(yōu)化(通常在函數(shù)編程語(yǔ)言中是需要的),對(duì)象也可以看作逃逸到被調(diào)用的子程序中泡躯。如果一種語(yǔ)言支持第一類型的延續(xù)性在Scheme和Standard ML of New Jersey中同樣如此)贮竟,部分調(diào)用棧也可能發(fā)生逃逸。
編譯器可以使用逃逸分析的結(jié)果作為優(yōu)化的基礎(chǔ):[1]

  • 將堆分配轉(zhuǎn)化為棧分配较剃。如果某個(gè)對(duì)象在子程序中被分配咕别,并且指向該對(duì)象的指針永遠(yuǎn)不會(huì)逃逸,該對(duì)象就可以在分配在棧上写穴,而不是在堆上惰拱。在有垃圾收集的語(yǔ)言中,這種優(yōu)化可以降低垃圾收集器運(yùn)行的頻率啊送。
  • 同步消除偿短。如果發(fā)現(xiàn)某個(gè)對(duì)象只能從一個(gè)線程可訪問,那么在這個(gè)對(duì)象上的操作可以不需要同步馋没。
  • 分離對(duì)象或標(biāo)量替換昔逗。如果某個(gè)對(duì)象的訪問方式不要求該對(duì)象是一個(gè)連續(xù)的內(nèi)存結(jié)構(gòu),那么對(duì)象的部分(或全部)可以不存儲(chǔ)在內(nèi)存披泪,而是存儲(chǔ)在CPU寄存器中纤子。

例如下面的代碼便是一個(gè)逃逸對(duì)象:

private static Bean bean;
public static void alloc() {
    bean = new Bean();
    bean.setParam(23);
....
}

其中bean字段可能被其它線程訪問到搬瑰,故屬于逃逸對(duì)象款票。
下面的代碼顯示了非逃逸對(duì)象:

public static void alloc() {
     bean = new Bean();
    bean.setParam(23);
....
}

啟用逃逸分析需要設(shè)置-server執(zhí)行程序,JVM參數(shù)如下:

-server -Xmx10m -Xms10m  -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations
  • -Xmx256m -Mms256m 分別制定了堆最大空間和堆最小空間為10M;
  • -XX:+DoEscapeAnalysis 啟用逃逸分析泽论;
  • -XX:+PrintGC 打印GC信息艾少;
  • -XX:-UseTLAB 關(guān)閉TLAB;
  • -XX:+EliminateAllocations 開啟標(biāo)量替換翼悴,允許將對(duì)象打散分配到棧上缚够。
    以上參數(shù)都是默認(rèn)啟用的幔妨。
    示例:
public class OnStackTest {

    public static class User {
        public int id = 0;
        public String name = "";
    }

    public static void alloc() {
        User user = new User();
        user.id = 10;
        user.name = "vincent";
    }

    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            alloc();
        }
        System.out.println(System.currentTimeMillis() - start);
    }
}

上面的代碼進(jìn)行了1000000000次調(diào)用,但是產(chǎn)生的GC日志很少:

[GC (Allocation Failure)  2047K->536K(9728K), 0.0008531 secs]
7

但如果關(guān)閉了逃逸分析谍椅,則會(huì)產(chǎn)生大量的GC日志误堡。例如將-XX:+DoEscapeAnalysis 替換成-XX:-DoEscapeAnalysis

四 方法區(qū)

方法區(qū)也是所有線程共享的內(nèi)存區(qū)域,用于保存系統(tǒng)類信息雏吭,例如字段锁施,方法常量池等。該區(qū)域大小決定了系統(tǒng)可以保存多少類杖们。但若定義了太多類同樣也會(huì)導(dǎo)致方法區(qū)溢出悉抵。
在JDK6和JDK7中,方法區(qū)可鏈接為永久區(qū)摘完,通過參數(shù)-XX:PermSize和-XX:MaxPermSize指定姥饰。但在JDK8中,永久區(qū)已經(jīng)被移除孝治,替代為元數(shù)據(jù)區(qū)列粪,可使用-XX:MaxMetaspaceSize參數(shù)指定,若不指定該參數(shù)谈飒,默認(rèn)情況下虛擬機(jī)會(huì)耗盡所有可用系統(tǒng)內(nèi)存篱竭,在VisualVM中可觀察永久區(qū):


永久區(qū)

元數(shù)據(jù)區(qū)溢出虛擬機(jī)會(huì)拋出java.lang.OutOfMemoryError: Metaspace異常。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末步绸,一起剝皮案震驚了整個(gè)濱河市掺逼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瓤介,老刑警劉巖吕喘,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異刑桑,居然都是意外死亡氯质,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門祠斧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)闻察,“玉大人,你說我怎么就攤上這事琢锋≡” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵吴超,是天一觀的道長(zhǎng)钉嘹。 經(jīng)常有香客問我,道長(zhǎng)鲸阻,這世上最難降的妖魔是什么跋涣? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任缨睡,我火速辦了婚禮,結(jié)果婚禮上陈辱,老公的妹妹穿的比我還像新娘奖年。我一直安慰自己,他們只是感情好沛贪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布拾并。 她就那樣靜靜地躺著,像睡著了一般鹏浅。 火紅的嫁衣襯著肌膚如雪嗅义。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天隐砸,我揣著相機(jī)與錄音之碗,去河邊找鬼。 笑死季希,一個(gè)胖子當(dāng)著我的面吹牛褪那,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播式塌,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼博敬,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了峰尝?” 一聲冷哼從身側(cè)響起偏窝,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎武学,沒想到半個(gè)月后祭往,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡火窒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年硼补,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熏矿。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡已骇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出票编,到底是詐尸還是另有隱情褪储,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布栏妖,位于F島的核電站乱豆,受9級(jí)特大地震影響奖恰,放射性物質(zhì)發(fā)生泄漏吊趾。R本人自食惡果不足惜宛裕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望论泛。 院中可真熱鬧揩尸,春花似錦、人聲如沸屁奏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)坟瓢。三九已至勇边,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間折联,已是汗流浹背粒褒。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诚镰,地道東北人奕坟。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像清笨,于是被迫代替她去往敵國(guó)和親月杉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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