? ? 此文以hotspot虛擬機(jī)為例來介紹jvm的內(nèi)存模型。先來一張圖吧,經(jīng)典的jvm內(nèi)存模型示意圖

1、堆
? ? 堆是用來存儲(chǔ)對(duì)象的內(nèi)存區(qū)域,被所有線程共享,java程序運(yùn)行時(shí)的對(duì)象都在堆中生存。堆也是jvm內(nèi)存中最大區(qū)域。堆又被分為新生代和老年代,其中新生代又分為eden區(qū)和兩個(gè)Sruvivor區(qū)。eden區(qū)是所有剛創(chuàng)建的對(duì)象的出生地,兩個(gè)Survivor去的大小是相同的,主要存放存活較久的對(duì)象(至少經(jīng)過一次gc而沒有被回收的對(duì)象)。當(dāng)新生代里的對(duì)象經(jīng)過多次垃圾(這個(gè)可以用-XX MaxTenuringThreshold設(shè)置進(jìn)入老年代對(duì)象需要的年齡)回收而沒有被銷毀的情況下就會(huì)被移到老年代區(qū)域里。因?yàn)槔夏甏鷧^(qū)域相對(duì)新生代也大了很多,因?yàn)榻^大部分的對(duì)象在創(chuàng)建后很快就沒用了,在新生代發(fā)生Minor GC的時(shí)候就會(huì)被銷毀,所以新生代同時(shí)也比老年代更頻繁的發(fā)生gc。老年代發(fā)生gc稱為full gc,在這個(gè)過程中整個(gè)jvm停擺,所以也要盡量避免full gc,這也是老念代設(shè)置比較大的另一個(gè)原因。關(guān)于jvm垃圾回收在下片篇文章里著重介紹,本文就不做過多說明了。
2、方法區(qū)
? ? 又叫靜態(tài)區(qū),跟堆一樣,被所有的線程共享,方法區(qū)包含所有的class和static變量在hotspot的jvm1.7之前的實(shí)現(xiàn)中String字符串常量池也在方法區(qū)里。在hotspot中方法區(qū)在jdk1.7之前又叫永久代,在jdk1.8之后方法區(qū)已經(jīng)移出了jvm內(nèi)存區(qū)域,改名叫做元空間(MetaSpace),其中原永久代中的類的元數(shù)據(jù)移到了本機(jī)內(nèi)存中(native memory),這樣類的元數(shù)據(jù)只受本地內(nèi)存大小限制,就沒有oom了,原永久代中的字符串常量池移到了堆中。
3、虛擬機(jī)棧
? ? java虛擬機(jī)棧是java執(zhí)行方法的內(nèi)存模型,每一個(gè)方法從調(diào)用到調(diào)用完成對(duì)應(yīng)著一個(gè)棧幀入棧都出棧的過程,一個(gè)棧幀(Stack Frame)包含局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接,方法出口等信息。
棧幀圖解模型如下。

局部變量表用于存放方法參數(shù)變量和局部變量在Class的方法表中指定了該變量表的最大容量。
操作數(shù)棧用于方法執(zhí)行中進(jìn)行算術(shù)運(yùn)算或者是調(diào)用其他的方法進(jìn)行參數(shù)傳遞的時(shí)候是通過操作數(shù)棧進(jìn)行的。
動(dòng)態(tài)連接每個(gè)棧幀都包含一個(gè)運(yùn)行時(shí)常量池中該棧幀所屬方法的引用,持有這個(gè)引用是為了支持方法調(diào)用過程中的動(dòng)態(tài)連接(Dynamic Linking)。
返回地址顧名思義就是這個(gè)方法執(zhí)行完以后要返回的指令地址,也就是調(diào)用者方法程序計(jì)數(shù)器地址。當(dāng)方法返回時(shí),可能進(jìn)行3個(gè)操作恢復(fù)上層方法的局部變量表和操作數(shù)棧把返回值壓入調(diào)用者調(diào)用者棧幀的操作數(shù)棧調(diào)整 PC 計(jì)數(shù)器的值以指向方法調(diào)用指令后面的一條指令。
4、程序計(jì)數(shù)器
程序計(jì)數(shù)器(program counter register)只占用了一塊比較小的內(nèi)存空間,至于小到什么程度呢,這樣說吧,有時(shí)可以忽略不計(jì)的。
可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼文件(class)的行號(hào)指示器。在虛擬機(jī)的世界中,字節(jié)碼解釋器就是通過改變計(jì)數(shù)器的值來選取下一條執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)都需要這玩意來實(shí)現(xiàn)的,NB嗎?
因?yàn)樘幚砥髟谝粋€(gè)確定是時(shí)刻只會(huì)執(zhí)行一個(gè)線程中的指令,線程切換后,是通過計(jì)數(shù)器來記錄執(zhí)行痕跡的,因而可以看出,程序計(jì)數(shù)器是每個(gè)線程私有的。如果執(zhí)行的是java方法,那么記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址的地址,如果是native方法,計(jì)數(shù)器的值為空(undefined)。這個(gè)內(nèi)存區(qū)域是唯一一個(gè)在java虛擬界規(guī)范中沒有規(guī)定任何OutOfMemoryError的情況的區(qū)域。至于為什么沒有這個(gè)異常呢,要是一個(gè)計(jì)數(shù)的功能在出這個(gè)異常,那么我也是醉了。