新聞中心
理解了 1+2的過(guò)程,就理解了Java虛擬機(jī)
作者:侯樹(shù)成 2020-04-16 10:55:03
云計(jì)算
虛擬化 在面試的時(shí)候,在問(wèn)到關(guān)于JVM相關(guān)的問(wèn)題,會(huì)發(fā)現(xiàn)不少的面試者都是機(jī)械的在記憶,稍一細(xì)問(wèn)就戛然而止。屬于死記硬背型的,估計(jì)是看書(shū)里記個(gè)大概的概念或者圖,并沒(méi)有理解含義。

創(chuàng)新互聯(lián)是一家專(zhuān)業(yè)提供煙臺(tái)企業(yè)網(wǎng)站建設(shè),專(zhuān)注與做網(wǎng)站、成都網(wǎng)站建設(shè)、H5頁(yè)面制作、小程序制作等業(yè)務(wù)。10年已為煙臺(tái)眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專(zhuān)業(yè)網(wǎng)絡(luò)公司優(yōu)惠進(jìn)行中。
在面試的時(shí)候,在問(wèn)到關(guān)于JVM相關(guān)的問(wèn)題,會(huì)發(fā)現(xiàn)不少的面試者都是機(jī)械的在記憶,稍一細(xì)問(wèn)就戛然而止。屬于死記硬背型的,估計(jì)是看書(shū)里記個(gè)大概的概念或者圖,并沒(méi)有理解含義。
實(shí)際上這塊內(nèi)容,看概念的時(shí)候能對(duì)照一個(gè)簡(jiǎn)單的程序分析,可以更好的理解。下面咱們開(kāi)始。
市面上常見(jiàn)的JVM書(shū)籍里,關(guān)于JVM的體系結(jié)構(gòu),一般劃分成以下幾個(gè)部分:
- 類(lèi)加載器
- 程序計(jì)數(shù)器(Program Counter Register,簡(jiǎn)稱(chēng)PC Register)
- Java 虛擬機(jī)棧(Java Virtual Machine Stacks)
- 堆(Heap)
- 方法區(qū)(Method Area)
- 運(yùn)行時(shí)常量池(Run-time Constant Pool)
- 本地方法棧(Native Method Stack)
- 棧幀(Stack Frame)
- 執(zhí)行引擎(Execution Engine)
其中2~7項(xiàng),又稱(chēng)為運(yùn)行時(shí)數(shù)據(jù)區(qū),畢竟這些東西只有JVM 跑起來(lái)才會(huì)創(chuàng)建。這個(gè)分類(lèi),基本都是參照 Java 虛擬機(jī)規(guī)范。
如果干巴巴的記概念沒(méi)啥意思,吃個(gè)飯可能就忘了。接下來(lái)用 1+2這個(gè)程序來(lái)試著理解它。
我們來(lái)看個(gè)初學(xué)Java 編程的時(shí)候都基本都寫(xiě)過(guò)的,類(lèi)似 Hello World的程序。
- public class HelloWorld {
- public static void main(String[] args) {
- int a = 1;
- int b = 2;
- int c = a + b;
- }
- }
先 javac 編譯之后,再用javap -verbose HelloWorld 來(lái)觀察一下, 你會(huì)看到類(lèi)似下面的輸出內(nèi)容:
- public class HelloWorld
- minor version: 0
- major version: 55
- flags: (0x0021) ACC_PUBLIC, ACC_SUPER
- this_class: #2 // HelloWorld
- super_class: #3 // java/lang/Object
- interfaces: 0, fields: 0, methods: 2, attributes: 1
- Constant pool:
- #1 = Methodref #3.#12 // java/lang/Object."
":()V - #2 = Class #13 // HelloWorld
- #3 = Class #14 // java/lang/Object
- #4 = Utf8
- #5 = Utf8 ()V
- #6 = Utf8 Code
- #7 = Utf8 LineNumberTable
- #8 = Utf8 main
- #9 = Utf8 ([Ljava/lang/String;)V
- #10 = Utf8 SourceFile
- #11 = Utf8 HelloWorld.java
- #12 = NameAndType #4:#5 // "
":()V - #13 = Utf8 HelloWorld
- #14 = Utf8 java/lang/Object
- {
- public HelloWorld();
- descriptor: ()V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=1, locals=1, args_size=1
- 0: aload_0
- 1: invokespecial #1 // Method java/lang/Object."
":()V - 4: return
- LineNumberTable:
- line 1: 0
- public static void main(java.lang.String[]);
- descriptor: ([Ljava/lang/String;)V
- flags: (0x0009) ACC_PUBLIC, ACC_STATIC
- Code:
- stack=2, locals=4, args_size=1
- 0: iconst_1
- 1: istore_1
- 2: iconst_2
- 3: istore_2
- 4: iload_1
- 5: iload_2
- 6: iadd
- 7: istore_3
- 8: return
- LineNumberTable:
- line 3: 0
- line 4: 2
- line 5: 4
- line 6: 8
- }
好嘞。咱們都知道,上面這些就是Java的字節(jié)碼。有了上面這個(gè)輸出的內(nèi)容,你把自己想像成虛擬機(jī),來(lái)運(yùn)行它,就理解了 Java 虛擬機(jī)里各個(gè)部分了。
首先,這部分內(nèi)容,要執(zhí)行,一定得先讀到內(nèi)存里,負(fù)載讀這些內(nèi)容的,就是虛擬機(jī)的類(lèi)加載器。
加載進(jìn)來(lái)的其實(shí)是個(gè)二進(jìn)制流,然后呢,需要把它整理成對(duì)應(yīng)格式的內(nèi)容才方便使用嘛。比如這個(gè)類(lèi)叫啥名字,繼承了誰(shuí),都有什么方法,方法名字叫啥,內(nèi)容是什么這些東西要找個(gè)地方放著。放哪好呢?方法區(qū)就是干這個(gè)的。
所謂的運(yùn)行時(shí)常量池也是方法區(qū)里的一塊區(qū)域。往上看Constant Pool 在運(yùn)行時(shí)會(huì)被解析成 Run-time Constant Pool。如果涉及到對(duì)其他類(lèi)的引用等等,會(huì)在加載之后再鏈接的時(shí)候,把這里面的符號(hào)引用轉(zhuǎn)化成直接引用。
另外一些部分呢?概括來(lái)講就是Java虛擬機(jī)棧,就是咱們常說(shuō)的棧,是用來(lái)執(zhí)行方法里的具體內(nèi)容的。這一部分其實(shí)可以這樣理解。Java 虛擬機(jī),和我們真實(shí)的物理機(jī)類(lèi)似,都會(huì)把程序提供的指令執(zhí)行,只不過(guò)虛擬機(jī)是一個(gè)提供了一套有限指令集的軟件。物理機(jī)基本都是基于寄存器執(zhí)行,而 Java 虛擬機(jī)的對(duì)于指令的執(zhí)行實(shí)現(xiàn)是基于棧的。
既然是棧,那棧里要放點(diǎn)什么?沒(méi)錯(cuò),是棧幀,英文是 Frames,就是咱們?cè)谑褂?IDE debug 的時(shí)候看到的那一層一層的內(nèi)容。
每個(gè)方法調(diào)用的時(shí)候,都會(huì)出現(xiàn)一幀,每一幀也是個(gè)結(jié)構(gòu),方法執(zhí)行用到的東西都在里面。比如在 Debug 的時(shí)候,一般都會(huì)看到每個(gè)變量和值, 這些變量稱(chēng)為本地變量(local variables),在上面的輸出內(nèi)容里也有stack=2, locals=4, args_size=1 我們看到locals就是本地變量,args_size是方法參數(shù)的長(zhǎng)度,還有一個(gè)就是操作數(shù)(stack),數(shù)值是棧的最大深度。
每個(gè)class 的任意一個(gè)方法里,都會(huì)有 frame ,它們都有自己的 local variables 本地變量表, 自己的operand stack 操作數(shù)棧,以及到run-time constant pool 運(yùn)行時(shí)常量池的引用。當(dāng)然,也可以有一些擴(kuò)展信息,例如debug info。
那具體上面簡(jiǎn)單的一個(gè) 1+2 這個(gè)操作,對(duì)應(yīng)到 jvm 指令有這些:
- 0: iconst_1
- 1: istore_1
- 2: iconst_2
- 3: istore_2
- 4: iload_1
- 5: iload_2
- 6: iadd
- 7: istore_3
- 8: return
具體當(dāng)前執(zhí)行到第幾條指令,需要有個(gè)標(biāo)識(shí),這個(gè)活兒讓程序計(jì)數(shù)器給干了。這小子一直指向下一條即將執(zhí)行的指令?;緱5膶?shí)現(xiàn),上面的指令大意是把常量1賦值給第一個(gè)變量,常量2賦值給第二個(gè)變量,之后,變量一入棧,變量二入棧,執(zhí)行iadd操作的時(shí)候,這兩個(gè)數(shù)據(jù)出棧,完成求和,再賦值給變量3,入棧,再返回。下次咱們細(xì)說(shuō)JVM指令的時(shí)候,再詳細(xì)說(shuō)說(shuō)。這些指令的執(zhí)行,當(dāng)然離不開(kāi)執(zhí)行引擎。
因?yàn)椴恍枰獔?zhí)行Native方法,所以我們一般不用本地方法棧,這是給類(lèi)似JNI這些本地方法實(shí)現(xiàn)準(zhǔn)備的。
你看,觀察了1+2的過(guò)程,基本Java 虛擬機(jī)的結(jié)構(gòu)是不是就理解了?:-) 如果還是記不住的話,你可以這樣想啊,Java 的世界里,經(jīng)常會(huì)說(shuō)到堆和棧。那棧用來(lái)存啥呢?想想你 debug 時(shí)候看到的那一層層的幀, 然后再想想今天的1+2的執(zhí)行,應(yīng)該就齊了。
本文轉(zhuǎn)載自微信公眾號(hào)「 Tomcat那些事兒」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 Tomcat那些事兒公眾號(hào)。
名稱(chēng)欄目:理解了 1+2的過(guò)程,就理解了Java虛擬機(jī)
URL標(biāo)題:http://www.dlmjj.cn/article/dpjhcco.html


咨詢
建站咨詢
