1 導(dǎo)讀
經(jīng)常會(huì)看到JVM內(nèi)存模型,其實(shí)說(shuō)的就是JVM運(yùn)營(yíng)時(shí)數(shù)據(jù)區(qū)的一個(gè)結(jié)構(gòu)摩渺,這篇文章主要記錄了對(duì)不同JDK版本運(yùn)營(yíng)時(shí)數(shù)據(jù)區(qū)的學(xué)習(xí)做一個(gè)總結(jié)偶洋。
2 運(yùn)行時(shí)數(shù)據(jù)區(qū)
2.1 什么是運(yùn)行時(shí)數(shù)據(jù)區(qū)
Java虛擬機(jī)在執(zhí)行程序的過(guò)程中,為了方便對(duì)程序進(jìn)行內(nèi)存管理幽邓,會(huì)將他管理的內(nèi)存區(qū)域劃分為幾個(gè)不同作用的區(qū)域。JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)包含以下圖中幾個(gè)區(qū)域火脉,其中堆牵舵、方法區(qū)是不同線程共享的柒啤,而程序計(jì)數(shù)器、本地方法棧畸颅、虛擬機(jī)棧是線程私有的担巩。
2.2 運(yùn)行時(shí)數(shù)據(jù)區(qū)介紹
2.2.1 程序計(jì)數(shù)器
簡(jiǎn)單來(lái)講,程序計(jì)數(shù)器是程序執(zhí)行當(dāng)前字節(jié)碼的位置信息没炒,每一個(gè)線程都有私有的程序計(jì)數(shù)器涛癌,這樣就可以保證每個(gè)線程可以獨(dú)立運(yùn)行自己的代碼,減少與其他線程的交互送火,提高代碼執(zhí)行效率拳话。
在《Java虛擬機(jī)規(guī)范》中,如果當(dāng)前執(zhí)行的不是本地方法种吸,那么記錄的是當(dāng)前指令的地址弃衍;如果執(zhí)行的是本地方法,那么這個(gè)值是未指定的(Undefind)坚俗,這塊區(qū)域很大镜盯,足以放下指令指針和本地指針。
2.2.2 虛擬機(jī)棧
每一個(gè)Java線程都有一個(gè)私有的虛擬機(jī)棧猖败,與線程生命周期同步速缆。當(dāng)每個(gè)方法被執(zhí)行的時(shí)候,Java虛擬機(jī)會(huì)同步創(chuàng)建一個(gè)棧幀恩闻,用于存儲(chǔ)局部變量表激涤、操作數(shù)棧、動(dòng)態(tài)鏈接判呕、方法出口等信息倦踢。每個(gè)方法被調(diào)用到調(diào)用結(jié)束,就對(duì)應(yīng)一個(gè)棧幀從入棧到出棧侠草。
public class JVMTest {
?
public static void main(String[] args) {
?
int i = 0;
String str = "abs";
JVMTest jvmTest = new JVMTest();
String m = jvmTest.m(str);
System.out.println(m);
}
?
private String m(String m) {
?
return m;
}
?
private static String m2(String m2) {
return m2;
}
}
-
局部變量表
局部變量表是一組變量值存儲(chǔ)空間辱挥,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量。
下面圖片是main方法的局部變量表边涕,第一個(gè)局部變量是形參0-args晤碘,后面依次是 1-i、2-str功蜓、3-jvmTest园爷、4-m變量
-
操作數(shù)棧
下面是main方法的字節(jié)碼指令
0 iconst_0 #將int類型常量值0壓入棧
1 istore_1 #將int類型值存入局部變量1
2 ldc #2 <a> #把常量池中的項(xiàng)壓入棧
4 astore_2 #將引用類型或returnAddress類型值存入局部變量2
5 new #3 <com/hp/test/thread/JVMTest> #創(chuàng)建一個(gè)JVMTest新對(duì)象
8 dup #復(fù)制棧頂部一個(gè)字長(zhǎng)內(nèi)容
9 invokespecial #4 <com/hp/test/thread/JVMTest.<init>> #調(diào)用初始化方法(之后會(huì)在類加載中詳細(xì)講解init方法)
12 astore_3 #將引用類型或returnAddress類型值存入局部變量3
13 aload_3 #從局部變量中裝載引用類型值
14 aload_2 #從局部變量中裝載引用類型值
15 invokespecial #5 <com/hp/test/thread/JVMTest.m> #調(diào)用m方法
18 astore 4 #將引用類型或returnAddress類型值存入局部變量4
20 getstatic #6 <java/lang/System.out> #從類中獲取靜態(tài)字段
23 aload 4 #從局部變量中裝在引用類型值
25 invokevirtual #7 <java/io/PrintStream.println> #調(diào)用println方法
28 return #從方法中返回,返回值為void
下面是m方法的字節(jié)碼指令
0 aload_1 #從局部變量1中裝載引用類型值
1 areturn #從方法中返回引用類型的數(shù)據(jù)
那么問(wèn)題來(lái)了式撼,明明方法m中形參是第一個(gè)局部變量童社,為什么是aload_1而不是aload_0?
這是因?yàn)槿绻?dāng)前幀是由構(gòu)造方法或者實(shí)例方法創(chuàng)建的著隆,那么該對(duì)象引用this將會(huì)存在index為0 的Slot處扰楼,非靜態(tài)方法呀癣,都會(huì)創(chuàng)建this的一個(gè)參數(shù),index為0弦赖,其余的參數(shù)是按照順序排放的项栏,static 方法被不可以使用this是因?yàn)閟tatic方法中沒(méi)有放this的index。
再來(lái)看一下m2方法字節(jié)碼指令
0 aload_0
1 areturn
-
方法出口
通俗的講蹬竖,每一個(gè)方法在執(zhí)行完或者拋出異常都需要回到上一個(gè)方法調(diào)用的地方沼沈,這就是方法出口。
-
動(dòng)態(tài)鏈接
通俗的講币厕,我們?cè)诜椒ㄕ{(diào)用時(shí)并不是直接將方法的信息保存在同一個(gè)棧幀中列另,而是指向該方法在常量池中的地址引用。
2.2.3 本地方法棧
? 本地方法棧(Native Method Stacks)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的劈榨,其區(qū)別只是虛擬機(jī) 棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù)访递,而本地方法棧則是為虛擬機(jī)使用到的本地(Native) 方法服務(wù)。
2.2.4 方法區(qū)
? 它用于存儲(chǔ)已被虛擬機(jī)加載 的類型信息同辣、常量拷姿、靜態(tài)變量、即時(shí)編譯器編譯后的代碼緩存等數(shù)據(jù)旱函。我們平時(shí)操作new對(duì)象時(shí)所需要的類信息就存儲(chǔ)在方法區(qū)响巢。(之后會(huì)在類加載中詳細(xì)講解)
? 在JDK1.8以前,方法區(qū)存放的位置可以理解成永久代棒妨,但是到了JDK1.8踪古,用元數(shù)據(jù)區(qū)(metaspace)取代了永久代。永久代是需要分配內(nèi)存大小的券腔,而元數(shù)據(jù)區(qū)在不指定大小的情況下伏穆,受限于物理內(nèi)存
2.2.5 堆
? 對(duì)于Java程序來(lái)說(shuō),堆是JVM管理內(nèi)存最大的一部分纷纫,此內(nèi)存部分是用于存放對(duì)象實(shí)例枕扫。根據(jù)對(duì)象的年齡,可以分為年輕代老年代辱魁。在年輕代中又可以分為Eden區(qū)烟瞧,Survive區(qū),之后要講到的GC也主要是針對(duì)堆進(jìn)行展開(kāi)染簇。
3 總結(jié)
想要對(duì)JVM有更深入的理解参滴,必須先搞清楚每個(gè)數(shù)據(jù)區(qū)的作用,以及代碼是如何在各個(gè)數(shù)據(jù)區(qū)串聯(lián)起來(lái)的锻弓。