1 JVM運行時數(shù)據(jù)區(qū)
JVM運行時數(shù)據(jù)區(qū)(JVM Runtime Area)其實就是指JVM在運行期間,其對計算機內(nèi)存空間的劃分和分配搀军。
2 程序計數(shù)器
是一塊較小的內(nèi)存空間膨俐,存儲下一條需要執(zhí)行的Java字節(jié)碼指令的地址(字節(jié)碼指令存儲在方法區(qū)中)勇皇。JVM的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實現(xiàn)的,在任何一個確定的時刻焚刺,一個處理器(對于多核處理器來說是一個內(nèi)核)都只能執(zhí)行一條線程中的指令敛摘。因此每條線程需要一個獨立的程序計數(shù)器,在能保證線程切換后能恢復(fù)到正確的執(zhí)行位置乳愉。
字節(jié)碼指令
在啟動的一個Java進程時兄淫,JVM會將class文件數(shù)據(jù)加載到方法區(qū)中。方法區(qū)中存儲Class文件數(shù)據(jù)其中包含的類信息和Java字節(jié)碼指令蔓姚。
源碼
public class ClassStructureMethod {
public void greeting() throws Exception {
try {
int a=1;
int b=1;
int c=a+b;
System.out.println(c);
}catch (Exception e){
System.out.println("catch");
}finally {
System.out.println("finally");
}
}
}
greeting方法Code屬性中字節(jié)碼指令
...省略
public void greeting() throws java.lang.Exception;
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=5, args_size=1
0: iconst_1
1: istore_1
2: iconst_1
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: istore_3
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: iload_3
12: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
15: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
18: ldc #4 // String finally
20: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: goto 59
26: astore_1
27: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
30: ldc #7 // String catch
32: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
35: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
38: ldc #4 // String finally
40: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
43: goto 59
46: astore 4
48: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
51: ldc #4 // String finally
53: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
56: aload 4
58: athrow
59: return
3 方法區(qū)
- 方法區(qū)主要用來存儲加載的Class文件信息拖叙,其中包括了常量池,類信息,字段信息,方法信息(字節(jié)碼指令),"指向類加載器的引言"赂乐,"指向Class的引言"薯鳍,"方法表"。
此區(qū)域的內(nèi)存回收目標(biāo)主要是針對無用類做卸載挨措,一般來說挖滤,回收效果難以令人滿意,尤其是類型的卸載浅役,條件相對苛刻斩松,但是這部分區(qū)域回收是有必要的。
當(dāng)方法無法滿足內(nèi)存需求時觉既,將會拋出OutOfMemoryError異常
4 Java虛擬機棧
Java虛擬機棧是一個后入先出棧惧盹。每一個線程創(chuàng)建時,JVM會為這個線程創(chuàng)建一個私有虛擬機棧瞪讼。
當(dāng)線程調(diào)用某個對象的方法時钧椰,JVM會相應(yīng)地創(chuàng)建一個棧幀壓入虛擬機棧中,返回時從虛擬機棧中取出符欠。線程方法的調(diào)用返回對應(yīng)著一個棧幀在虛擬機棧中的入棧和出棧的過程嫡霞。
當(dāng)前虛擬機正在執(zhí)行的方法的棧幀被稱為"當(dāng)前活動的棧幀",永遠位于虛擬機棧頂部。
4.1 虛擬機棧中的方法調(diào)用
案例
public class Demo3 {
public void test1() {
System.out.println("我是test1的方法");
test2();
}
public void test2() {
System.out.println("我是test2的方法");
test3();
}
public void test3() {
System.out.println("我是test3的方法");
}
public static void main(String[] args) {
Demo3 demo3=new Demo3();
demo3.test1();
}
}
虛擬機棧中的流程
4.2 棧幀結(jié)構(gòu)
4.2.1 局部變量表
局部變量表(Local Variable Table)是一組變量值存儲空間(類似于數(shù)組)希柿,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量诊沪。局部變量表的容量以變量槽(Slot)為最小單位,如果局部變量表類似于數(shù)組曾撤,那么變量槽(Slot)就相當(dāng)于數(shù)組中一個數(shù)據(jù)塊的大小端姚。
局部變量表中變量最大數(shù)量在將源碼文件編譯成Class文件時就以確定,存儲在方法的Code屬性locals中挤悉。
需要注意的是如果是實例方法(非static的方法)渐裸,那么局部變量表中第0位索引的Slot默認是用于傳遞方法所屬對象實例的引用。
局部變量表存儲數(shù)據(jù)類型
八大數(shù)據(jù)類型(boolean、byte橄仆、char、short衅斩、int盆顾、float、long畏梆、double)
對象引用
returnAddress類型(指向了一條字節(jié)碼指令的地址)
存儲數(shù)據(jù)方式
32位虛擬機中一個Slot可以存放一個32位以內(nèi)的數(shù)據(jù)類型(boolean您宪、byte、char奠涌、short宪巨、int、float溜畅、reference和returnAddress八種)捏卓。對于long、double這種64位數(shù)據(jù)類型 值需要用兩個(Slot)來存儲.
在局部變量表中慈格,boolean怠晴、byte、char浴捆、short引用類型這五種類型蒜田,在虛擬機棧中空間和int是一樣的都占用8個字節(jié),而long选泻、double占用16個字節(jié)冲粤。
4.2.2 操作數(shù)棧
操作數(shù)棧是用來存儲Java字節(jié)碼指令計算過程中的參數(shù)和結(jié)果的數(shù)據(jù)結(jié)構(gòu)。Java虛擬機的解釋執(zhí)行引擎被稱為"基于棧的執(zhí)行引擎"页眯,其中所指的棧就是-操作數(shù)棧梯捕。
棧的深度已經(jīng)在將源碼文件編譯成Class文件時就以確定,存儲在方法的Code屬性stack中窝撵。
操作數(shù)棧的存儲
操作數(shù)棧也是被組織成一個以字長為單位的數(shù)組科阎,不同的是,它不是通過索引來訪問忿族,而是通過標(biāo)準(zhǔn)的棧操作—壓棧和出椔啾浚—來訪問的。比如道批,如果某個指令把一個值壓入到操作數(shù)棧中错英,稍后另一個指令就可以彈出這個值來使用。
虛擬機在操作數(shù)棧中存儲數(shù)據(jù)的方式和在局部變量區(qū)中是一樣的隆豹。對于非long椭岩、double都使用8個字節(jié)存儲占用1個變量槽,對于long、double都使用16個字節(jié)存儲占用2個變量槽判哥,這也應(yīng)證了Java虛擬機對數(shù)據(jù)的操作就是在操作數(shù)棧和局部變量表中相互傳遞献雅。
操作數(shù)棧的案例
begin
iload_0 // 將第一個int型本地變量推送至棧頂
iload_1 // 將第二個int型本地變量推送至棧頂
iadd // 將棧頂兩int型數(shù)值相加并將結(jié)果壓入棧頂
istore_2 // 將棧頂int型數(shù)值存入第三個本地變量
end
前兩個指令iload_0和iload_1將存儲在局部變量中索引為0和1的整數(shù)壓入操作數(shù)棧中,其后iadd指令從操作數(shù)棧中彈出那兩個整數(shù)相加塌计,再將結(jié)果壓入操作數(shù)棧挺身。第四條指令istore_2則從操作數(shù)棧中彈出結(jié)果,并把它存儲到局部變量區(qū)索引為2的位置锌仅。下圖詳細表述了這個過程中局部變量和操作數(shù)棧的狀態(tài)變化章钾,圖中沒有使用的局部變量區(qū)和操作數(shù)棧區(qū)域以空白表示。
4.2.3 方法返回地址
方法返回在虛擬機中流程
恢復(fù)上層方法的局部變量表和操作數(shù)棧
把返回值壓入調(diào)用者調(diào)用者棧幀的操作數(shù)棧
調(diào)整 PC 計數(shù)器的值以指向方法調(diào)用指令后面的一條指令
方法的正常返回
當(dāng)執(zhí)行遇到返回指令return热芹,會將返回值傳遞給上層的方法調(diào)用者贱傀,這種退出的方式稱為正常完成出口(Normal Method Invocation Completion),一般來說伊脓,調(diào)用者的PC計數(shù)器可以作為返回地址府寒。
方法的異常返回
當(dāng)執(zhí)行遇到異常,并且當(dāng)前方法體內(nèi)沒有得到處理报腔,就會導(dǎo)致方法退出椰棘,此時是沒有返回值的,稱為異常完成出口(Abrupt Method Invocation Completion)榄笙,返回地址要通過異常處理器表來確定邪狞。
4.2.4 動態(tài)鏈接
在了解動態(tài)鏈接之前我們需要了解如下幾個概念,“符號引用”茅撞,“直接引用”帆卓,“靜態(tài)鏈接”
符號引用
在 class 文件被加載至 Java 虛擬機之前,這個類無法知道其他類及其方法米丘、字段所對應(yīng)的具體地址剑令,甚至不知道自己方法、字段的地址拄查。因此吁津,每當(dāng)需要引用這些成員時,Java 編譯器會生成一個符號引用堕扶。符號引用是方法區(qū)中常量池中一類常量碍脏。符號引用用來描述類中,類稍算,接口典尾,字段,方法的描述信息糊探。
對于一個方法調(diào)用钾埂,編譯器會生成一個包含目標(biāo)方法所在類的名字河闰、目標(biāo)方法的名字、接收參數(shù)類型以及返回值類型的符號引用褥紫,來指代所要調(diào)用的方法姜性。
解析階段的目的,正是將這些符號引用解析成為實際引用髓考。如果符號引用指向一個未被加載的類部念,或者未被加載類的字段或方法,那么解析將觸發(fā)這個類的加載(但未必觸發(fā)這個類的鏈接以及初始化绳军。)
#define CONSTANT_Class 1 //對一個類或接口的符號引用
#define CONSTANT_Fieldref 2 //對一個字段的符號引用
#define CONSTANT_Methodref 3 //對一個類中方法的符號引用
#define CONSTANT_InterfaceMethodref 4 //對一個接口中方法的符號引用
- 對于一個類或接口的描述信息包括類或接口的全限定名稱
#8 = Class #39 // jvm/ClassStructureMethod
- 對于一個字段描述信息包括字段所在的類以及描述符印机,描述符包括字段的名稱和類型
#2 = Fieldref #3.#19 // jvm/TestClass.m:I
- 對于一個方法描述信息包括方法所在的類以及描述符矢腻,描述符包括方法的名稱门驾,參數(shù)和返回類型
#1 = Methodref #9.#30 // java/lang/Object."<init>":()V
因此符號引用以一組符號來描述所引用的目標(biāo),符號引用與虛擬機實現(xiàn)的內(nèi)存布局無關(guān)多柑,引用的目標(biāo)并不一定已經(jīng)加載到了內(nèi)存中奶是。
直接引用
直接引用可以是直接指向目標(biāo)在內(nèi)存中的指針,其值中存儲目標(biāo)在內(nèi)存中的地址竣灌。這里所謂的目標(biāo)就是指符號引用表示的類聂沙,接口,字段初嘹,方法及汉。也就是說我們程序執(zhí)行中需要的類在沒有加載到內(nèi)存中時可以使用符號引用來表示。
當(dāng)我們將一個類加載Class文件加載到Java虛擬機的內(nèi)存中屯烦。在解析時會將符號引用轉(zhuǎn)換為直接引用坷随。
靜態(tài)鏈接
靜態(tài)鏈接就是在類加載階段將符號引用轉(zhuǎn)換為直接引用的過程。
在Java中靜態(tài)鏈接就是類加載機制中的一個過程—解析,將class文件中的一部分符號引用直接解析為直接引用的過程驻龟。同時我們也稱它為“解析調(diào)用”
由于Java中多態(tài)的特性温眉,并不所有的符號引用都能在類加載階段轉(zhuǎn)換為直接引用。因此靜態(tài)鏈接需要如下條件翁狐。
方法在程序真正運行之前就有一個可確定的調(diào)用版本类溢,并且這個方法的調(diào)用版本在運行期是不可改變的÷独粒可以概括為:編譯期可知闯冷、運行期不可變。
其中invokestatic和invokespecial指令調(diào)用的方法懈词,都可以在解析階段中確定唯一的調(diào)用版本窃躲,符合這個條件的有靜態(tài)方法、私有方法钦睡、實例構(gòu)造器蒂窒、父類方法
動態(tài)鏈接
動態(tài)鏈接就是在運行期間將符號引用轉(zhuǎn)換為直接引用的過程躁倒。
Java中天生可以動態(tài)擴展的語言特性就是依賴動態(tài)加載和動態(tài)鏈接這個特點實現(xiàn)的。
動態(tài)擴展就是在運行期可以動態(tài)修改字節(jié)碼洒琢,也就是反射機制與cglib
5 本地方法棧
本地方法棧(Native Method Stack)與虛擬機棧所發(fā)揮的作用類似秧秉,它們之間的區(qū)別是:虛擬機棧為虛擬機執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機使用到的Native方法服務(wù)衰抑。
6 堆
對于大多數(shù)應(yīng)用程序而言象迎,Java堆(Heap)是Java虛擬機所管理的內(nèi)存中最大的一塊,它是被所有線程共享的一塊內(nèi)存區(qū)域呛踊,在虛擬機啟動時創(chuàng)建砾淌。此內(nèi)存區(qū)域唯一的目的是存放對象實例,幾乎所有的對象實例都在這里分配內(nèi)存谭网。
Java虛擬機規(guī)范中描述道:所有的對象實例以及數(shù)組都要在堆上分配般码,但是隨著JIT編譯器的發(fā)展和逃逸分析技術(shù)逐漸成熟奥秆,棧上分配、標(biāo)量替換優(yōu)化技術(shù)將會導(dǎo)致一些微妙的變化發(fā)生,所有的對象都在堆上分配的定論也并不“絕對”了捌袜。
Java堆與垃圾回收器
Java堆是垃圾回收器管理的主要區(qū)域苗缩,因此被稱為“GC堆”(Garbage Collected Heap)扶认。
從內(nèi)存回收角度看
由于目前收集器基本采用分代收集算法唇跨,所以Java堆可細分為:新生代和老年代。
從內(nèi)存分配角度來看
由于堆是線程共享的必然會存在并發(fā)的問題层坠,JVM為每一個線程固定了一個存儲區(qū)域線程私有的分配緩沖區(qū)(TLAB:Thread Local Allocation Buffer)殖妇。
7 直接內(nèi)存
直接內(nèi)存(Direct Memory)并不是虛擬機運行時數(shù)據(jù)的一部分,也不是Java虛擬機規(guī)范中定義的內(nèi)存區(qū)域破花。但這部分內(nèi)存也被頻繁運用谦趣,而卻可能導(dǎo)致OutOfMemoryError異常出現(xiàn)。
直接內(nèi)存一般適用適用NIO 適用堆外內(nèi)存的情況