
JVM(Java Virtual Machine Java 虛擬機(jī))是一種用于計(jì)算設(shè)備的規(guī)范,基于這套規(guī)范,許多團(tuán)隊(duì)開發(fā)了多種不同的虛擬機(jī)實(shí)現(xiàn),目前使用范圍最廣的是從 Sun 公司開始,到 Oracle 后一直在使用 的 HotSpot 以及 Open JDK,二者一個(gè)由 Oracle 公司維護(hù),一個(gè)由開源社區(qū)維護(hù),使用起來并沒有太大差別。
$java -version
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode)
根據(jù) Java 虛擬機(jī)規(guī)范 規(guī)定,Java 虛擬機(jī)所管理的內(nèi)存將會(huì)包括以下幾個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)域,如圖所示:

- 程序計(jì)數(shù)器(Program Counter Register)
線程私有。程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。
字節(jié)碼解釋器工作時(shí)就是通過改變這個(gè)計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)登記處功能都需要依賴這個(gè)計(jì)數(shù)器的值來完成。
為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每個(gè)線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各條線程之間的計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ)。
如果正在執(zhí)行的是 Native 方法,則計(jì)數(shù)器為空。
程序計(jì)數(shù)器,是唯一一個(gè)在java虛擬機(jī)規(guī)范中沒有規(guī)定任何Out Of Memory Error的區(qū)域。
- Java虛擬機(jī)棧(Java Virtual Machine Stacks )
也是線程私有的,生命周期與線程相同。
虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:
每一個(gè)方法從調(diào)用到完成,都對(duì)應(yīng)著一個(gè)棧幀(Stack Frame)在虛擬機(jī)中入棧到出棧的過程,棧幀用于局部變量表(Local Variables)、操作數(shù)棧(Operand Stack)、指向當(dāng)前方法所屬的類的運(yùn)行時(shí)常量池的引用(Reference to runtime constant pool)、方法返回地址(ReturnAddress)和一些額外的附加信息。
棧幀結(jié)構(gòu)
- 局部變量表(Current Variable Table)
局部變量表是用來存儲(chǔ)方法中的局部變量(包括在方法中聲明的非靜態(tài)變量以及函數(shù)形參)。對(duì)于基本數(shù)據(jù)類型的變量,則直接存儲(chǔ)它的值,對(duì)于引用類型的變量,則存的是指向?qū)ο蟮囊?。局部變量表的大小在編譯器就可以確定其大小了,因此在程序執(zhí)行期間局部變量表的大小是不會(huì)改變的。- 操作數(shù)棧(Operand Stack)
操作數(shù)棧也常被稱為操作棧。和局部變量區(qū)一樣,操作數(shù)棧也是被組織成一個(gè)以字長(zhǎng)為單位的數(shù)組。但是和前者不同的是,它不是通過索引來訪問,而是通過標(biāo)準(zhǔn)的棧操作—壓棧和出棧—來訪問的。比如,如果某個(gè)指令把一個(gè)值壓入到操作數(shù)棧中,稍后另一個(gè)指令就可以彈出這個(gè)值來使用。虛擬機(jī)在操作數(shù)棧中存儲(chǔ)數(shù)據(jù)的方式和在局部變量區(qū)中是一樣的:如int、long、float、double、reference和returnType的存儲(chǔ)。對(duì)于byte、short以及char類型的值在壓入到操作數(shù)棧之前,也會(huì)被轉(zhuǎn)換為int。程序中的所有計(jì)算過程都是在借助于操作數(shù)棧來完成的。- 動(dòng)態(tài)鏈接(Dynamic Linking)
指向運(yùn)行時(shí)常量池的引用,因?yàn)樵诜椒▓?zhí)行的過程中有可能需要用到類中的常量,所以必須要有一個(gè)引用指向運(yùn)行時(shí)常量。- 返回地址(Return Adress)
方法返回地址,當(dāng)一個(gè)方法執(zhí)行完畢之后,要返回之前調(diào)用它的地方,因此在棧幀中必須保存一個(gè)方法返回地址。平常我們把java分為堆內(nèi)存和棧內(nèi)存,其中的“棧”就是現(xiàn)在講的虛擬機(jī)棧,或者說是虛擬機(jī)棧中局部變量表部分。
對(duì)于java虛擬機(jī)棧,有兩種異常情況:
- 如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常;
- 如果虛擬機(jī)棧在動(dòng)態(tài)擴(kuò)展時(shí),無(wú)法申請(qǐng)到足夠的內(nèi)存,就會(huì)拋出OutOfMemoryError。
Java虛擬機(jī)的解釋執(zhí)行引擎被稱為“基于棧的執(zhí)行引擎”,其中所指的“?!本褪遣僮鲾?shù)棧。
因此我們也稱Java虛擬機(jī)是基于棧的,這點(diǎn)不同于Android虛擬機(jī),Android虛擬機(jī)是基于寄存器的。
- 本地方法棧(Native Method Stack)
線程私有。
本地方法棧和虛擬機(jī)棧所發(fā)揮的作用非常相似,它們之間的區(qū)別主要是,虛擬機(jī)棧是為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù)的,而本地方法棧則為虛擬機(jī)使用到的Native方法服務(wù)。
與虛擬機(jī)棧類似,本地方法棧也會(huì)拋出StackOverflowError和OutOfMemoryError異常。
- Java堆(Java Heap)
所有線程共享。
Java堆在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊。
Java堆的唯一目的就是存放對(duì)象實(shí)例和數(shù)組。
Java堆是垃圾收集器管理的主要區(qū)域,因此也被稱為“GC堆”。
從內(nèi)存回收的角度來看,由于現(xiàn)在的收集器大都采用分代收集算法,所以Java堆可以細(xì)分為:新生代和老年代;
再細(xì)分一點(diǎn):Eden空間、From Survivor空間、To Survivor空間等。
從內(nèi)存分配角度來看,線程共享的Java堆可以劃分出多個(gè)線程私有的分配緩沖區(qū)。
但是不管怎么劃分,哪個(gè)區(qū)域,存儲(chǔ)的都是對(duì)象實(shí)例。
Java堆物理上不需要連續(xù)的內(nèi)存,只要邏輯上連續(xù)即可。
如果堆中沒有內(nèi)存完成實(shí)例分配,并且也無(wú)法再擴(kuò)展時(shí),將會(huì)拋出OutOfMemoryError異常。
- 方法區(qū)(Method Area)
所有線程共享。
用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
方法區(qū)結(jié)構(gòu)上和 Heap 一致,也有一個(gè)別名叫做Non-Heap(非堆),用于與Java堆區(qū)分。
對(duì)于HotSpot虛擬機(jī)來說,方法區(qū)又習(xí)慣稱為“永久代”(Permancent Generation 現(xiàn)被元空間(Metaspace)代替),但這只是對(duì)于HotSpot虛擬機(jī)來說的,其他虛擬機(jī)的實(shí)現(xiàn)上并沒有這個(gè)概念。
相對(duì)而言,垃圾收集行為在這個(gè)區(qū)域比較少出現(xiàn),但也并非不會(huì)來收集,這個(gè)區(qū)域的內(nèi)存回收目標(biāo)主要是針對(duì)常量池的回收和對(duì)類型的卸載上。
當(dāng)方法區(qū)無(wú)法滿足內(nèi)存需求時(shí)會(huì)拋出 OutOfMemoryError 異常。
- 運(yùn)行時(shí)常量池(Runtime Constant Pool)
在Class文件中除了類的字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池,用來存儲(chǔ)編譯期間生成的字面量和符號(hào)引用。
運(yùn)行時(shí)常量池是方法區(qū)的一部分,用于存放編譯期生成的各種字面量和符號(hào)。
避免了頻繁的創(chuàng)建和銷毀對(duì)象而影響系統(tǒng)性能,實(shí)現(xiàn)了對(duì)象的共享,節(jié)省了內(nèi)存空間和運(yùn)行時(shí)間。
值得說明的是,運(yùn)行時(shí)常量池具備動(dòng)態(tài)特性,常量不止在編譯期可以產(chǎn)生,在運(yùn)行期間也可以產(chǎn)生,比如String的intern方法。
參見:
java 1.8 SE 官方文檔: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5
JVM內(nèi)存結(jié)構(gòu) VS Java內(nèi)存模型 VS Java對(duì)象模型:http://www.hollischuang.com/archives/2509
什么是HotSpot VM & 深入理解Java虛擬機(jī) JVM:https://www.cnblogs.com/charlesblc/p/5993804.html
Java虛擬機(jī)的內(nèi)存結(jié)構(gòu):https://www.duyidong.com/2018/01/31/jvm/
