JVM实战与原理---字节码执行引擎
JVM實戰與原理
目錄
字節碼執行引擎
1. 方法區
2. 棧幀
2.1 局部變量表
2.2 操作數棧
2.3 動態連接
2.4 方法返回地址
字節碼執行引擎
章節目的:虛擬機是如何找到正確的方法,如何執行方法內的字節碼,以及執行代碼時涉及的內存結構
引言:前面我們已經知道虛擬機加載類的過程,并且開辟出怎么樣的內存結構,那么接下來虛擬機是怎么執行類代碼的呢?
我們以Person類為例,講解其從main開發到結束,字節碼執行引擎是如何進行的。
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;} }其對應的Class文件內容反編譯結果如下
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. 方法區
在類加載后,會將Class文件中的類信息、常量、靜態變量以及每個方法的信息和對應的字節碼指令放入方法區內存中,并將其中的static修飾的變量通過<clinit>方法進行初始化。
2. 棧幀
作用:支持虛擬機進行方法調用和方法執行的數據結構,存儲了方法的局部變量表、操作數棧、動態連接和方法返回地址
一個方法對應著一個棧幀,同時當前方法位于棧頂,例如進入main方法,此時main方法在棧頂,此時main方法中調用了methodA方法,則methodA入棧,位于棧頂。
下面我們詳細介紹棧幀的組成部分
2.1 局部變量表
作用:存放方法參數和方法內部定義的局部變量。
局部變量表以容量槽Slot為最小單位,其中boolean、byte、char、short、int、float、reference或returnAddress為一個Slot,long和double為兩個Slot。一般來說一個Slot占用32位長度的內存空間,其中reference類型表示對一個對象實例的引用,通過引用,能查找到對象在堆中數據存放的起始地址索引。returnAddress已經很少見了,已被異常表代替。
實例:Person類的methodA方法中,isShow、count和str便是存放在局部變量表中,其中isShow為boolean類型,count為int類型,str則是reference類型。
如果執行的是實例(非static)方法,那局部變量表的第0位索引的Slot默認是方法所屬對象實例的引用,在方法中用關鍵字this來訪問到這個隱含的參數
2.2 操作數棧
作用:用于存放方法執行過程中,字節碼指令進行入棧和出棧操作時,寫入和提取的內容。
boolean、byte、char、short、int、float所占的棧容量為1,long和double所占的棧容量為2。
實例:Person類的methodA方法中,count = count + 1;便是在操作數棧中進行,先將count入棧,接著1入棧,然后執行棧頂頭兩個元素相加的指令,然后出棧。
2.3 動態連接
符號引用一部分會在類加載階段或者第一次使用的時候就轉化為直接引用,這種轉化稱為靜態解析。
另外一部分將在每一次運行期間轉化為直接引用,這部分成為動態連接。
2.4 方法返回地址
作用:方法退出之后,需要返回到方法被調用的位置,程序才能繼續執行。
方法有兩種退出方式
1. 正常完成出口:執行引擎遇到任意一個方法返回的字節碼指令,這時候可能會有返回值傳遞給上層的調用者,此種退出方式,棧幀中會保存調用者PC計數器的值。
2. 異常完成出口:當方法執行過程遇到異常,且異常沒有在方法體內得到處理,只要在方法的異常表中沒有搜索到匹配的異常處理器,就會導致方法退出,此種退出方式,返回地址是要通過異常處理器表來確定,棧幀中不保存這部分信息。
Person類的方法執行過程:
虛擬機會自動尋找到main方法進行執行。執行方法,則會將main方法生成棧幀壓入虛擬機棧中。然后main方法的字節碼如下
? ? ? ? ?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方法中調用了methodA方法,則會將methodA壓入棧,此時methodA的局部變量表會存有isShow、count及str變量。
methodA的字節碼如下
?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類信息在加載階段會被加載至方法區中,new指令會在堆中開辟空間存放對象實例,引用str壓入操作數棧棧頂。
dup指令會將棧頂的str復制并壓入棧頂
ldc將空字符串從方法區的常量池中推送至棧頂
invokespecial指令則會調用String類的初始化方法
astore_2指令將棧頂引用型數值存入第三個本地變量中
這樣,經過以上四個指令,str則被初始化成功,其類信息在方法區中,引用在棧的局部變量表中,實例信息則在堆中。
最后的areturn,則是方法的退出指令,此時方法返回地址記錄著main方法調用methodA方法的地址,在methodA方法結束返回后,便會根據這個返回地址返回到main方法中。
main方法中調用println打印后,調用return指令同樣也會進行方法退出,至此,整個main方法便執行結束了。
總結
以上是生活随笔為你收集整理的JVM实战与原理---字节码执行引擎的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何免费下载和安装Windows 11
- 下一篇: JVM实战与原理---内存回收策略