1 前言
什么是JVM?我們來看一下維基百科的答案
A Java virtual machine (JVM) is a virtual machine that enables a computer to run Java programs as well as programs written in other languages and compiled to Java bytecode. The JVM is detailed by a specification that formally describes what is required of a JVM implementation. Having a specification ensures interoperability of Java programs across different implementations so that program authors using the Java Development Kit (JDK) need not worry about idiosyncrasies of the underlying hardware platform.
Java虛擬機(jī)(JVM)是一種虛擬機(jī)檐什,它使計(jì)算機(jī)能夠運(yùn)行Java程序以及用其他語言編寫的程序(如Groovy,Scala)输枯,并編譯成Java字節(jié)碼。JVM由規(guī)范描述了JVM實(shí)現(xiàn)所需的規(guī)范。使用規(guī)范確保Java程序在不同實(shí)現(xiàn)之間的互操作性,以便使用Java開發(fā)工具包(JDK)的程序作者不必?fù)?dān)心底層硬件平臺(tái)的特質(zhì)。
其中虛擬機(jī)指的是
指通過軟件模擬的具有完整硬件系統(tǒng)功能的、運(yùn)行在一個(gè)完全隔離環(huán)境中的完整計(jì)算機(jī)系統(tǒng)。
我們常見的虛擬機(jī)有VM Ware
,Virtual Box
,JVM
而今天我們要介紹的內(nèi)容是虛擬機(jī)中很重要的一個(gè)概念耳幢,虛擬機(jī)的內(nèi)存結(jié)構(gòu)。
2 虛擬機(jī)內(nèi)存結(jié)構(gòu)
其中最重要的概念是 方法區(qū),JAVA堆睛藻,JAVA棧启上,指令計(jì)數(shù)器(PC)
指令計(jì)數(shù)器(PC)
每個(gè)線程都有獨(dú)立的程序計(jì)數(shù)器,各線程的互不影響店印,用于存儲(chǔ)下一條執(zhí)行的虛擬機(jī)指令地址(對(duì)于Native方法則為空undefined)-
JAVA棧(VM Stack)
我們知道棧是一種先進(jìn)后出
的數(shù)據(jù)結(jié)構(gòu)冈在,它有點(diǎn)像機(jī)關(guān)槍的彈夾先被放進(jìn)去的子彈最后被打出來。
JVM會(huì)為每一個(gè)線程創(chuàng)建一個(gè)棧按摘,JAVA中的棧是以棧幀為基本的數(shù)據(jù)單元,在一個(gè)線程棧里面會(huì)有很多個(gè)棧幀(Frame
)包券,每一個(gè)棧幀對(duì)應(yīng)一個(gè)方法。例如:我們一般在main方法寫的程序炫贤,叫做主線程中main方法的棧幀溅固。而這個(gè)main方法的棧幀存儲(chǔ)著,該方法運(yùn)行時(shí)所需要的數(shù)據(jù)兰珍。當(dāng)main方法調(diào)用其他方法例如max()方法发魄。那么max方法所對(duì)應(yīng)的棧幀就會(huì)進(jìn)行壓棧操作,成為當(dāng)前的棧幀。當(dāng)max()方法執(zhí)行結(jié)束之后俩垃,當(dāng)前的棧幀就會(huì)出棧,main方法重新成為當(dāng)前的棧幀汰寓。
那么一個(gè)棧幀主要存儲(chǔ)著哪些數(shù)據(jù)呢口柳?主要包含下面的數(shù)據(jù)- 局部變量表
- 操作數(shù)棧
- 方法返回地址
- 動(dòng)態(tài)鏈接
2.1 局部變量表:
局部變量表是一組變量值存儲(chǔ)空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量有滑,其中存放的數(shù)據(jù)的類型是編譯期可知的各種基本數(shù)據(jù)類型跃闹、對(duì)象引用(reference)和(returnAddress)類型(它指向了一條字節(jié)碼指令的地址)。局部變量表所需的內(nèi)存空間在編譯期間完成計(jì)算的毛好,即在Java程序被編譯成Class文件時(shí)望艺,就確定了所需分配的最大局部變量表的容量。當(dāng)進(jìn)入一個(gè)方法時(shí)肌访,這個(gè)方法需要在棧中分配多大的局部變量空間是完全確定的找默,在方法運(yùn)行期間不會(huì)改變局部變量表的大小。2.2 操作數(shù)棧:
操作數(shù)棧又常被稱為操作棧吼驶,操作數(shù)棧的最大深度也是在編譯的時(shí)候就確定了惩激。32位數(shù)據(jù)類型所占的棧容量為1, 64位數(shù)據(jù)類型所占的棧容量為2。當(dāng)一個(gè)方法開始執(zhí)行時(shí)蟹演,它的操作棧是空的风钻,在方法的執(zhí)行過程中,會(huì)有各種字節(jié)碼指令(比如:加操作酒请、賦值元算等)向操作棧中寫入和提取內(nèi)容骡技,也就是入棧和出棧操作。Java虛擬機(jī)的解釋執(zhí)行引擎稱為“基于棧的執(zhí)行引擎”羞反,其中所指的“棽茧”就是操作數(shù)棧囤萤。因此我們也稱Java虛擬機(jī)是基于棧的,這點(diǎn)不同于Android虛擬機(jī)喝滞,Android虛擬機(jī)是基于寄存器的阁将。基于棧的指令集最主要的優(yōu)點(diǎn)是可移植性強(qiáng)右遭,主要的缺點(diǎn)是執(zhí)行速度相對(duì)會(huì)慢些做盅;而由于寄存器由硬件直接提供,所以基于寄存器指令集最主要的優(yōu)點(diǎn)是執(zhí)行速度快窘哈,主要的缺點(diǎn)是可移植性差吹榴。2.3 動(dòng)態(tài)鏈接:
每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池(在方法區(qū)中,后面介紹)中該棧幀所屬方法的引用滚婉,持有這個(gè)引用是為了支持方法調(diào)用過程中的動(dòng)態(tài)連接图筹。Class文件的常量池中存在有大量的符號(hào)引用,字節(jié)碼中的方法調(diào)用指令就以常量池中指向方法的符號(hào)引用為參數(shù)让腹。這些符號(hào)引用远剩,一部分會(huì)在類加載階段或第一次使用的時(shí)候轉(zhuǎn)化為直接引用(如 final、static 域等)骇窍,稱為靜態(tài)解析瓜晤,另一部分將在每一次的運(yùn)行期間轉(zhuǎn)化為直接引用,這部分稱為動(dòng)態(tài)連接腹纳。2.4 方法返回地址:
當(dāng)一個(gè)方法被執(zhí)行后痢掠,有兩種方式退出該方法:執(zhí)行引擎遇到了任意一個(gè)方法返回的字節(jié)碼指令或遇到了異常,并且該異常沒有在方法體內(nèi)得到處理嘲恍。無論采用何種退出方式足画,在方法退出之后,都需要返回到方法被調(diào)用的位置佃牛,程序才能繼續(xù)執(zhí)行淹辞。方法返回時(shí)可能需要在棧幀中保存一些信息,用來幫助恢復(fù)它的上層方法的執(zhí)行狀態(tài)俘侠。一般來說桑涎,方法正常退出時(shí),調(diào)用者的PC計(jì)數(shù)器的值就可以作為返回地址兼贡,棧幀中很可能保存了這個(gè)計(jì)數(shù)器值攻冷,而方法異常退出時(shí),返回地址是要通過異常處理器來確定的遍希,棧幀中一般不會(huì)保存這部分信息等曼。方法退出的過程實(shí)際上等同于把當(dāng)前棧幀出站,因此退出時(shí)可能執(zhí)行的操作有:恢復(fù)上層方法的局部變量表和操作數(shù)棧,如果有返回值禁谦,則把它壓入調(diào)用者棧幀的操作數(shù)棧中胁黑,調(diào)整PC計(jì)數(shù)器的值以指向方法調(diào)用指令后面的一條指令。
-
JAVA堆(Heap)
所有線程共享的內(nèi)存區(qū)域州泊,用于存放對(duì)象實(shí)例 -
方法區(qū)(Method Area)
線程共享丧蘸,用于存放已加載的類、常量遥皂、靜態(tài)變量力喷、JIT編譯后的代碼等數(shù)據(jù)。
對(duì)于HotSpot虛擬機(jī)用戶而言演训,經(jīng)常將方法區(qū)稱為永生代(Permanent Generation)弟孟,是因?yàn)镠otSpot虛擬機(jī)用永生代實(shí)現(xiàn)方法區(qū),用GC管理方法區(qū)样悟。 -
綜合案例
public class Sample {
private String name;
public Sample(String name) {
this.name = name;
}
public void printName() {
System.out.println(name);
}
}
public class appMain {
public static void main(String[] args) {
Sample test1 = new Sample("測(cè)試1");
Sample test2 = new Sample("測(cè)試2");
test1.printName();
test2.printName();
}
}
程序的執(zhí)行流程如下拂募。
- appMain類信息(即appMain.class對(duì)象)通過類加載器加載進(jìn)方法區(qū)。
-
test1,test2
是自定義類Sample
類的兩個(gè)引用窟她,放置在主線程main
方法對(duì)應(yīng)的棧幀中陈症。 - Sample類的兩個(gè)
實(shí)例
放置在堆區(qū)中。 - 分別調(diào)用在
test1,test2
,關(guān)于Sample存儲(chǔ)在方法區(qū)中的printName()
方法震糖。 - 返回
main
方法爬凑,程序結(jié)束。