JVM实战与原理---字节码执行引擎
JVM實(shí)戰(zhàn)與原理
目錄
字節(jié)碼執(zhí)行引擎
1. 方法區(qū)
2. 棧幀
2.1 局部變量表
2.2 操作數(shù)棧
2.3 動(dòng)態(tài)連接
2.4 方法返回地址
字節(jié)碼執(zhí)行引擎
章節(jié)目的:虛擬機(jī)是如何找到正確的方法,如何執(zhí)行方法內(nèi)的字節(jié)碼,以及執(zhí)行代碼時(shí)涉及的內(nèi)存結(jié)構(gòu)
引言:前面我們已經(jīng)知道虛擬機(jī)加載類(lèi)的過(guò)程,并且開(kāi)辟出怎么樣的內(nèi)存結(jié)構(gòu),那么接下來(lái)虛擬機(jī)是怎么執(zhí)行類(lèi)代碼的呢?
我們以Person類(lèi)為例,講解其從main開(kāi)發(fā)到結(jié)束,字節(jié)碼執(zhí)行引擎是如何進(jìn)行的。
class Person {public static void main(String[] args) {System.out.println(methodA(true, 0));}public static String methodA(boolean isShow, int count) {String str = new String("");if(isShow) {str = new String("show");count = count + 1;}return str;} }其對(duì)應(yīng)的Class文件內(nèi)容反編譯結(jié)果如下
class Personminor version: 0major version: 52flags: ACC_SUPER Constant pool:#1 = Methodref #10.#23 // java/lang/Object."<init>":()V#2 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/Print Stream;#3 = Methodref #9.#26 // Person.methodA:(ZI)Ljava/lang/Strin g;#4 = Methodref #27.#28 // java/io/PrintStream.println:(Ljava/ lang/String;)V#5 = Class #29 // java/lang/String#6 = String #30 //#7 = Methodref #5.#31 // java/lang/String."<init>":(Ljava/la ng/String;)V#8 = String #32 // show#9 = Class #33 // Person#10 = Class #34 // java/lang/Object#11 = Utf8 <init>#12 = Utf8 ()V#13 = Utf8 Code#14 = Utf8 LineNumberTable#15 = Utf8 main#16 = Utf8 ([Ljava/lang/String;)V#17 = Utf8 methodA#18 = Utf8 (ZI)Ljava/lang/String;#19 = Utf8 StackMapTable#20 = Class #29 // java/lang/String#21 = Utf8 SourceFile#22 = Utf8 Person.java#23 = NameAndType #11:#12 // "<init>":()V#24 = Class #35 // java/lang/System#25 = NameAndType #36:#37 // out:Ljava/io/PrintStream;#26 = NameAndType #17:#18 // methodA:(ZI)Ljava/lang/String;#27 = Class #38 // java/io/PrintStream#28 = NameAndType #39:#40 // println:(Ljava/lang/String;)V#29 = Utf8 java/lang/String#30 = Utf8#31 = NameAndType #11:#40 // "<init>":(Ljava/lang/String;)V#32 = Utf8 show#33 = Utf8 Person#34 = Utf8 java/lang/Object#35 = Utf8 java/lang/System#36 = Utf8 out#37 = Utf8 Ljava/io/PrintStream;#38 = Utf8 java/io/PrintStream#39 = Utf8 println#40 = Utf8 (Ljava/lang/String;)V {Person();descriptor: ()Vflags:Code:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init> ":()V4: returnLineNumberTable:line 1: 0public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=3, locals=1, args_size=10: getstatic #2 // Field java/lang/System.out:Ljav a/io/PrintStream;3: iconst_14: iconst_05: invokestatic #3 // Method methodA:(ZI)Ljava/lang/S tring;8: invokevirtual #4 // Method java/io/PrintStream.prin tln:(Ljava/lang/String;)V11: returnLineNumberTable:line 4: 0line 5: 11public static java.lang.String methodA(boolean, int);descriptor: (ZI)Ljava/lang/String;flags: ACC_PUBLIC, ACC_STATICCode:stack=3, locals=3, args_size=20: new #5 // class java/lang/String3: dup4: ldc #6 // String6: invokespecial #7 // Method java/lang/String."<init> ":(Ljava/lang/String;)V9: astore_210: iload_011: ifeq 2814: new #5 // class java/lang/String17: dup18: ldc #8 // String show20: invokespecial #7 // Method java/lang/String."<init> ":(Ljava/lang/String;)V23: astore_224: iload_125: iconst_126: iadd27: istore_128: aload_229: areturnLineNumberTable:line 8: 0line 9: 10line 10: 14line 11: 24line 13: 28StackMapTable: number_of_entries = 1frame_type = 252 /* append */offset_delta = 28locals = [ class java/lang/String ] } SourceFile: "Person.java"1. 方法區(qū)
在類(lèi)加載后,會(huì)將Class文件中的類(lèi)信息、常量、靜態(tài)變量以及每個(gè)方法的信息和對(duì)應(yīng)的字節(jié)碼指令放入方法區(qū)內(nèi)存中,并將其中的static修飾的變量通過(guò)<clinit>方法進(jìn)行初始化。
2. 棧幀
作用:支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),存儲(chǔ)了方法的局部變量表、操作數(shù)棧、動(dòng)態(tài)連接和方法返回地址
一個(gè)方法對(duì)應(yīng)著一個(gè)棧幀,同時(shí)當(dāng)前方法位于棧頂,例如進(jìn)入main方法,此時(shí)main方法在棧頂,此時(shí)main方法中調(diào)用了methodA方法,則methodA入棧,位于棧頂。
下面我們?cè)敿?xì)介紹棧幀的組成部分
2.1 局部變量表
作用:存放方法參數(shù)和方法內(nèi)部定義的局部變量。
局部變量表以容量槽Slot為最小單位,其中boolean、byte、char、short、int、float、reference或returnAddress為一個(gè)Slot,long和double為兩個(gè)Slot。一般來(lái)說(shuō)一個(gè)Slot占用32位長(zhǎng)度的內(nèi)存空間,其中reference類(lèi)型表示對(duì)一個(gè)對(duì)象實(shí)例的引用,通過(guò)引用,能查找到對(duì)象在堆中數(shù)據(jù)存放的起始地址索引。returnAddress已經(jīng)很少見(jiàn)了,已被異常表代替。
實(shí)例:Person類(lèi)的methodA方法中,isShow、count和str便是存放在局部變量表中,其中isShow為boolean類(lèi)型,count為int類(lèi)型,str則是reference類(lèi)型。
如果執(zhí)行的是實(shí)例(非static)方法,那局部變量表的第0位索引的Slot默認(rèn)是方法所屬對(duì)象實(shí)例的引用,在方法中用關(guān)鍵字this來(lái)訪問(wèn)到這個(gè)隱含的參數(shù)
2.2 操作數(shù)棧
作用:用于存放方法執(zhí)行過(guò)程中,字節(jié)碼指令進(jìn)行入棧和出棧操作時(shí),寫(xiě)入和提取的內(nèi)容。
boolean、byte、char、short、int、float所占的棧容量為1,long和double所占的棧容量為2。
實(shí)例:Person類(lèi)的methodA方法中,count = count + 1;便是在操作數(shù)棧中進(jìn)行,先將count入棧,接著1入棧,然后執(zhí)行棧頂頭兩個(gè)元素相加的指令,然后出棧。
2.3 動(dòng)態(tài)連接
符號(hào)引用一部分會(huì)在類(lèi)加載階段或者第一次使用的時(shí)候就轉(zhuǎn)化為直接引用,這種轉(zhuǎn)化稱(chēng)為靜態(tài)解析。
另外一部分將在每一次運(yùn)行期間轉(zhuǎn)化為直接引用,這部分成為動(dòng)態(tài)連接。
2.4 方法返回地址
作用:方法退出之后,需要返回到方法被調(diào)用的位置,程序才能繼續(xù)執(zhí)行。
方法有兩種退出方式
1. 正常完成出口:執(zhí)行引擎遇到任意一個(gè)方法返回的字節(jié)碼指令,這時(shí)候可能會(huì)有返回值傳遞給上層的調(diào)用者,此種退出方式,棧幀中會(huì)保存調(diào)用者PC計(jì)數(shù)器的值。
2. 異常完成出口:當(dāng)方法執(zhí)行過(guò)程遇到異常,且異常沒(méi)有在方法體內(nèi)得到處理,只要在方法的異常表中沒(méi)有搜索到匹配的異常處理器,就會(huì)導(dǎo)致方法退出,此種退出方式,返回地址是要通過(guò)異常處理器表來(lái)確定,棧幀中不保存這部分信息。
Person類(lèi)的方法執(zhí)行過(guò)程:
虛擬機(jī)會(huì)自動(dòng)尋找到main方法進(jìn)行執(zhí)行。執(zhí)行方法,則會(huì)將main方法生成棧幀壓入虛擬機(jī)棧中。然后main方法的字節(jié)碼如下
? ? ? ? ?0: getstatic ? ? #2 ? ? ? ? ? ? ? ? ?// Field java/lang/System.out:Ljava/io/PrintStream;
? ? ? ? ?3: iconst_1
? ? ? ? ?4: iconst_0
? ? ? ? ?5: invokestatic ?#3 ? ? ? ? ? ? ? ? ?// Method methodA:(ZI)Ljava/lang/String;
? ? ? ? ?8: invokevirtual #4 ? ? ? ? ? ? ? ? ?// Method java/io/PrintStream.println:(Ljava/lang/String;)V
? ? ? ? 11: return
main方法中調(diào)用了methodA方法,則會(huì)將methodA壓入棧,此時(shí)methodA的局部變量表會(huì)存有isShow、count及str變量。
methodA的字節(jié)碼如下
?0: new ? ? ? ? ? #5 ? ? ? ? ? ? ? ? ?// class java/lang/String
?3: dup
?4: ldc ? ? ? ? ? #6 ? ? ? ? ? ? ? ? ?// String
?6: invokespecial #7 ? ? ? ? ? ? ? ? ?// Method java/lang/String."<init>?
?9: astore_2
10: iload_0
11: ifeq ? ? ? ? ?28
14: new ? ? ? ? ? #5 ? ? ? ? ? ? ? ? ?// class java/lang/String
17: dup
18: ldc ? ? ? ? ? #8 ? ? ? ? ? ? ? ? ?// String show
20: invokespecial #7 ? ? ? ? ? ? ? ? ?// Method java/lang/String."<init>?
23: astore_2
24: iload_1
25: iconst_1
26: iadd
27: istore_1
28: aload_2
29: areturn
String類(lèi)信息在加載階段會(huì)被加載至方法區(qū)中,new指令會(huì)在堆中開(kāi)辟空間存放對(duì)象實(shí)例,引用str壓入操作數(shù)棧棧頂。
dup指令會(huì)將棧頂?shù)膕tr復(fù)制并壓入棧頂
ldc將空字符串從方法區(qū)的常量池中推送至棧頂
invokespecial指令則會(huì)調(diào)用String類(lèi)的初始化方法
astore_2指令將棧頂引用型數(shù)值存入第三個(gè)本地變量中
這樣,經(jīng)過(guò)以上四個(gè)指令,str則被初始化成功,其類(lèi)信息在方法區(qū)中,引用在棧的局部變量表中,實(shí)例信息則在堆中。
最后的areturn,則是方法的退出指令,此時(shí)方法返回地址記錄著main方法調(diào)用methodA方法的地址,在methodA方法結(jié)束返回后,便會(huì)根據(jù)這個(gè)返回地址返回到main方法中。
main方法中調(diào)用println打印后,調(diào)用return指令同樣也會(huì)進(jìn)行方法退出,至此,整個(gè)main方法便執(zhí)行結(jié)束了。
總結(jié)
以上是生活随笔為你收集整理的JVM实战与原理---字节码执行引擎的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 如何免费下载和安装Windows 11
- 下一篇: JVM实战与原理---内存回收策略