類加載-位元組碼
位元組碼檔案
- 所謂的方法入口就是你調用的這個方法在實際記憶體中的位址,一段方法的代碼塊,一定是存在記憶體中的某個位址,那麼這個代碼塊開始的位址,也就是所謂的方法的入口,因為你要從這個位址進入到你要調用的這個方法,然後去執行該方法的邏輯。而所謂的靜态綁定,就是因為這個類的這個方法不能夠被改變了,也就是說你不能重寫這個方法了,類如 私有的,靜态的,final的,即使你是子類繼承了他,但是你也不能夠重寫這個方法,你要想調用這個方法,你隻能以父類去調用這個方法,或者你想調用,你調用的也隻能是父類中的這個方法,是以這個方法在記憶體中的位址是不會變的,無論你的執行個體對象是什麼,他的方法入口隻能是父類中唯一的那一個,但是動态綁定就是你必須在運作的時候才能确定這個執行個體對象調用的這個方法的入口在哪,因為這個方法可以被重寫,重寫了之後,他阿德方法體就不固定,裡面的方法邏輯就可能發生改變,那麼你就沒辦法使用同一個方法入口(記憶體位址)去讀取方法代碼塊,你也就沒辦法在編譯期決定你要調用的這個方法的入口(也就是位址)你隻能在執行個體化對象以後,通過執行個體化對象,去找類的資訊(這個類的資訊如果是Animal dog = new Dog())那麼她找到的類資訊是Dog的類資訊而不是Animal 的資訊,然後通過類資訊中的虛方法表,定位到這個類的這個方法的實際方法入口(也就是代碼塊的記憶體位址的位置),
- u4 magic 【ca fe ba be】 0~3 個位元組 表示它是否是【class】類型檔案 不同檔案不同
- minor_version 小版本号
- major_version 主版本号 00 34(52) java 8
- constant_pool_count 常量池資訊
- constant_pool[constant_pool_count-1] 常量池資訊
- access_flags 傳回修飾 ;public private
- this_class 類自己的資訊 包名之類的
- super_class 父類資訊
- interfaces_count 接口資訊
- interfaces[interfaces_count] 接口資訊
- fileds_count 成員變量資訊 ,靜态變量
- fileds[fileds_count] 成員變量資訊 ,靜态變量
- methods_count 成員方法 靜态方法
- methods[methods_count] 成員方法 靜态方法
- attributes_count 附加屬性資訊
- attributes[attribytes_count] 附加屬性資訊
1. 常量池
2. 通路修飾符
3. 成員變量資訊
4. Java工具
/**
* @program: jvmstudy
* @description: Demo
* @author: SunYang
* @create: 2021-07-19 21:10
**/
public class HelloWord {
public static void main(String[] args) {
System.out.println("hello word");
}
}
C:\ideaworkspace\jvmstudy\out\production\jvmstudy>javap -v HelloWord.class
Classfile /C:/ideaworkspace/jvmstudy/out/production/jvmstudy/HelloWord.class
Last modified 2021-7-19; size 529 bytes
MD5 checksum a8c57b9db7fea3b2ecf897fb791d54e0
Compiled from "HelloWord.java"
public class HelloWord
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #23 // hello word
#4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #26 // HelloWord
#6 = Class #27 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 LHelloWord;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 SourceFile
#19 = Utf8 HelloWord.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = Class #28 // java/lang/System
#22 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
#23 = Utf8 hello word
#24 = Class #31 // java/io/PrintStream
#25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V
#26 = Utf8 HelloWord
#27 = Utf8 java/lang/Object
#28 = Utf8 java/lang/System
#29 = Utf8 out
#30 = Utf8 Ljava/io/PrintStream;
#31 = Utf8 java/io/PrintStream
#32 = Utf8 println
#33 = Utf8 (Ljava/lang/String;)V
{
public HelloWord();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LHelloWord;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String hello word
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 9: 0
line 10: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
}
SourceFile: "HelloWord.java"
5. 圖解執行流程
/**
* @program: jvmstudy
* @description: Demo
* @author: SunYang
* @create: 2021-07-19 21:18
**/
public class Demo3_1 {
public static void main(String[] args) {
int a = 10; // Short.MAX_VALUE 最大值以内是和位元組碼指令存在一起的
int b = Short.MAX_VALUE + 1; // 超過了Short.MAX_VALUE(32767)就會存儲在常量池中
int c = a +b;
System.out.println(c);
}
}
C:\ideaworkspace\jvmstudy\out\production\jvmstudy>javap -v Demo3_1.class
Classfile /C:/ideaworkspace/jvmstudy/out/production/jvmstudy/Demo3_1.class
Last modified 2021-7-19; size 583 bytes
MD5 checksum 2fdb45a6f9da4a1a69b2ba7f94a19baf
Compiled from "Demo3_1.java"
public class Demo3_1
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #7.#25 // java/lang/Object."<init>":()V
#2 = Class #26 // java/lang/Short
#3 = Integer 32768
#4 = Fieldref #27.#28 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Methodref #29.#30 // java/io/PrintStream.println:(I)V
#6 = Class #31 // Demo3_1
#7 = Class #32 // java/lang/Object
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 LocalVariableTable
#13 = Utf8 this
#14 = Utf8 LDemo3_1;
#15 = Utf8 main
#16 = Utf8 ([Ljava/lang/String;)V
#17 = Utf8 args
#18 = Utf8 [Ljava/lang/String;
#19 = Utf8 a
#20 = Utf8 I
#21 = Utf8 b
#22 = Utf8 c
#23 = Utf8 SourceFile
#24 = Utf8 Demo3_1.java
#25 = NameAndType #8:#9 // "<init>":()V
#26 = Utf8 java/lang/Short
#27 = Class #33 // java/lang/System
#28 = NameAndType #34:#35 // out:Ljava/io/PrintStream;
#29 = Class #36 // java/io/PrintStream
#30 = NameAndType #37:#38 // println:(I)V
#31 = Utf8 Demo3_1
#32 = Utf8 java/lang/Object
#33 = Utf8 java/lang/System
#34 = Utf8 out
#35 = Utf8 Ljava/io/PrintStream;
#36 = Utf8 java/io/PrintStream
#37 = Utf8 println
#38 = Utf8 (I)V
{
public Demo3_1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LDemo3_1;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: bipush 10
2: istore_1
3: ldc #3 // int 32768
5: istore_2
6: iload_1
7: iload_2
8: iadd
9: istore_3
10: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
13: iload_3
14: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
17: return
LineNumberTable:
line 9: 0
line 10: 3
line 11: 6
line 12: 10
line 13: 17
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 args [Ljava/lang/String;
3 15 1 a I
6 12 2 b I
10 8 3 c I
}
SourceFile: "Demo3_1.java"
常量池載入運作時常量池
- JVM的類加載器把躺在硬碟上的這個類位元組碼檔案加載到記憶體中
- 常量池資料則載入運作時常量池
方法的位元組碼載入方法區
main線程開始運作,配置設定棧幀空間
- 将棧頂元素全部緩存在實體CPU的寄存器中,以此降低對記憶體的讀/寫次數,提升執行引擎的執行效率。
-
棧幀:一個方法對應一塊棧幀記憶體區域。
棧幀中包含:局部變量表、操作數棧、動态連結、方法出口等等
局部變量表:存放程式運作時局部變量的表。是一塊記憶體區域
操作數棧:程式在運作時,對資料進行臨時操作的記憶體區域。資料結構也是棧。是一塊記憶體區域
方法出口:方法被調用的位置(方法調用方調用方法結束後,方法調用方程式計數器的值)。是一塊記憶體區域
-
A a= new A();
int b = a.fun();
- 類加載進方法區記憶體時,會有相應的類元資訊(包含指令碼);
- 在new對象時(堆記憶體),會在對象頭裡面存放一個對象所屬類的類元資訊位址(這樣就可以知道這個對象是屬于哪個類,該指針指向方法區記憶體中對應類的類元資訊–類型指針)
- 動态連結:
-
方法在JVM記憶體中都是以靜态符号的形式存在于常量池
存放着靜态符号對應的JVM指令碼在記憶體中入口的位置(首位址)(程式在運作到a.fun()時才知道這個位置的)
怎麼知道的?因為在new對象的時候,會在對象的對象頭中存放一個對象所屬類的類型指針,當執行到對象的某個方法時,就會根據這個類型指針動态的找到這個方法所對應的類元資訊的指令碼,存放到動态連結中。(前提:在程式運作過程中),在程式為運作到此處時,動态連結中儲存的是靜态符号
- JVM執行到含有動态連結的指令碼(調用方法時)–>通過靜态符号(在常量池中)(兩部分組成:類和成員:類.成員)–>JVM執行到靜态符号時,會解析靜态符号,将靜态符号轉換成其對應的直接引用(對應的指令碼)。這些指令碼實際也是靜态的,但是一旦運作,就會将這些指令碼裝載到方法區記憶體區域,此時該指令碼就會有一個入口的記憶體位址,類型指針會通過靜态符号找到該靜态符号所擁有的JVM記憶體位址指令碼記憶體位址指針,然後把記憶體位址指針存到動态連結中
-
- 局部變量表長度為什麼是4,因為有四個局部變量args, a, b,c
-
LocalVariableTable: Start Length Slot Name Signature 0 18 0 args [Ljava/lang/String; 3 15 1 a I 6 12 2 b I 10 8 3 c I
- 為什麼操作數棧的深度是2?因為這個操作數棧的最大深度在編譯期就可以決定,就像我們編譯完成後顯示的一樣
-
stack=2, locals=4, args_size=1 // 在編譯完成時就可以确定,因為操作數棧的寬度為4個位元組(32bit)确定,加入一個int4位元組正好一個深度,如果是小類型會自動轉換成int類型,是因為長度不夠4個位元組,必須補齊4個位元組才夠一個深度,加減乘除會彈出兩個加減乘除指定是彈出棧頂的兩個元素,并将計算的結果重新壓入棧頂,是以對站深度的貢獻= -2 + 1=-1,當然這裡指的是int,如果是别的類型,也可以計算,按照位元組大小去算。
執行引擎開始執行位元組碼
- bipush 10(操作數棧的寬度為4個位元組固定)
- 将一個byte壓入操作數棧中(因為byte長度是一個位元組)是以會補齊4個位元組(如果是正數用0,如果是負數最後一個用1)因為這裡的10 在-128和127之間
- sipush 将一個short壓入操作數棧,(其長度會補齊4個位元組)
- ldc (int不用push)是将常量池中的int壓入操作數棧
- ldc2_w 将一個long 壓入操作數棧(分兩次壓入,因為long是8個位元組)
- 這裡小的數字都是和位元組碼指令存在一起,在方法區中,超過short範圍的數字存入常量池
- istore_1
-
- 編譯好了以後就已經确定了每個變量的槽位
- 将操作數棧棧頂資料彈出,存入局部變量表的slot 1的位置,也就是槽位1的位置
- bipush 10 istore_1 對應着 int a = 10;
- ldc #3
- 從常量池中加載#3資料到操作數棧
- 常量優化,Short.MAX_VALUE = 32767, 是以int b = 32768 = Short.MAX_VALUE+1在編譯器就已經計算好了(常量折疊)
- istore_2
- 彈出棧頂元素放入2号槽位
- 給 相當于 int b = Short.MAX_VALUE+1 = 32762
- 然後執行a+b操作,但是局部變量表中不能執行a+b操作,要在操作數棧中進行
- iload_1
- 将局部變量表中的1号槽位的a變量加載到操作數棧中(入棧)
- 将局部變量表中的2号槽位的b變量加載到操作數棧中(入棧)
- iadd
- 将操作數棧中的兩變量相加後彈出,放入計算後的值
- istore_3
- int c = a+b;
- getstatic #4
- 1 :在方法區中讀取getstatic#4 指令
- 2 :然後去常量池中找到這個靜态成員變量的引用#4
- 3 :然後根據這個靜态成員變量的引用位址找到堆中的對象
- 4 : 然後将堆中對象的位址引用(對象頭中會有對象的位址資訊,32位系統4個位元組(32bit)64位系統8個位元組(64bit)但是正常都是4個位元組,因為四個位元組足夠用了,64位的系統也别壓縮成了4個位元組,但是可以自己更改參數改回8個位元組)加載到操作數棧中
- iload_3
- invokevirtual #5
- 找到常量池中的#5
- 然後找到#5是要執行方法java/io/PrintStream.println:(I)V
- 然後定位到方法區中的java/io/PrintStream.println:(I)V
- 新的方法,生成新的棧幀(配置設定locals,stack等)
- 傳遞參數,執行新棧幀中的位元組碼
- 然後将這個方法的棧幀彈出虛拟機棧
- 清除main操作數棧内容
- return
- 完成main方法調用,彈出main棧幀
- 程式結束
練習 分析 i++
-
/** * @program: jvmstudy * @description: Demo * @author: SunYang * @create: 2021-07-20 19:33 **/ public class IADDDemo { public static void main(String[] args) { int a = 10; int b = a++ + ++a + a--; // 10 + 12 + 12 System.out.println(a); // 12-1 System.out.println(b); } }
-
C:\ideaworkspace\jvmstudy\out\production\jvmstudy>javap -v IADDDemo.class Classfile /C:/ideaworkspace/jvmstudy/out/production/jvmstudy/IADDDemo.class Last modified 2021-7-20; size 561 bytes MD5 checksum 968bc16520cb05df7bc182203cbb1a24 Compiled from "IADDDemo.java" public class IADDDemo minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #5.#22 // java/lang/Object."<init>":()V #2 = Fieldref #23.#24 // java/lang/System.out:Ljava/io/PrintStream; #3 = Methodref #25.#26 // java/io/PrintStream.println:(I)V #4 = Class #27 // IADDDemo #5 = Class #28 // java/lang/Object #6 = Utf8 <init> #7 = Utf8 ()V #8 = Utf8 Code #9 = Utf8 LineNumberTable #10 = Utf8 LocalVariableTable #11 = Utf8 this #12 = Utf8 LIADDDemo; #13 = Utf8 main #14 = Utf8 ([Ljava/lang/String;)V #15 = Utf8 args #16 = Utf8 [Ljava/lang/String; #17 = Utf8 a #18 = Utf8 I #19 = Utf8 b #20 = Utf8 SourceFile #21 = Utf8 IADDDemo.java #22 = NameAndType #6:#7 // "<init>":()V #23 = Class #29 // java/lang/System #24 = NameAndType #30:#31 // out:Ljava/io/PrintStream; #25 = Class #32 // java/io/PrintStream #26 = NameAndType #33:#34 // println:(I)V #27 = Utf8 IADDDemo #28 = Utf8 java/lang/Object #29 = Utf8 java/lang/System #30 = Utf8 out #31 = Utf8 Ljava/io/PrintStream; #32 = Utf8 java/io/PrintStream #33 = Utf8 println #34 = Utf8 (I)V { public IADDDemo(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 7: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this LIADDDemo; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: bipush 10 2: istore_1 3: iload_1 4: iinc 1, 1 7: iinc 1, 1 10: iload_1 11: iadd 12: iload_1 13: iinc 1, -1 16: iadd 17: istore_2 18: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 21: iload_1 22: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 25: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 28: iload_2 29: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 32: return LineNumberTable: line 9: 0 line 10: 3 line 11: 18 line 12: 25 line 13: 32 LocalVariableTable: Start Length Slot Name Signature 0 33 0 args [Ljava/lang/String; 3 30 1 a I 18 15 2 b I } SourceFile: "IADDDemo.java"
- 分析
- 注意iinc指令是直接在局部變量slot(槽位)上進行運算的
- a++ 是先執行iload将a 加載到操作數棧中然後執行在slot(槽位上)執行iinc
- ++a 是先在槽位上執行iinc然後在執行iload加載到操作數棧中
- iinc 1,1(第一個1是指要自增哪個槽位,這裡是第一個槽位,第二個1是指自增幾,這裡是1)直接在slot上操作
條件判斷指令
構造方法
《cinit》()V
- 整個類的構造方法
-
/** * @program: jvmstudy * @description: Demo * @author: SunYang * @create: 2021-07-20 19:33 **/ public class IADDDemo { static int i = 10; static { i = 10; } static { i = 30; } public static void main(String[] args) { } }
-
C:\ideaworkspace\jvmstudy\out\production\jvmstudy>javap -v IADDDemo.class Classfile /C:/ideaworkspace/jvmstudy/out/production/jvmstudy/IADDDemo.class Last modified 2021-7-20; size 470 bytes MD5 checksum a3ec2d8b9a2621f7dc1fed37c5fa1dd6 Compiled from "IADDDemo.java" public class IADDDemo minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #4.#21 // java/lang/Object."<init>":()V #2 = Fieldref #3.#22 // IADDDemo.i:I #3 = Class #23 // IADDDemo #4 = Class #24 // java/lang/Object #5 = Utf8 i #6 = Utf8 I #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 LIADDDemo; #14 = Utf8 main #15 = Utf8 ([Ljava/lang/String;)V #16 = Utf8 args #17 = Utf8 [Ljava/lang/String; #18 = Utf8 <clinit> #19 = Utf8 SourceFile #20 = Utf8 IADDDemo.java #21 = NameAndType #7:#8 // "<init>":()V #22 = NameAndType #5:#6 // i:I #23 = Utf8 IADDDemo #24 = Utf8 java/lang/Object { static int i; descriptor: I flags: ACC_STATIC public IADDDemo(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 7: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this LIADDDemo; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=0, locals=1, args_size=1 0: return LineNumberTable: line 20: 0 LocalVariableTable: Start Length Slot Name Signature 0 1 0 args [Ljava/lang/String; static {}; descriptor: ()V flags: ACC_STATIC Code: stack=1, locals=0, args_size=0 0: bipush 10 2: putstatic #2 // Field i:I 5: bipush 10 7: putstatic #2 // Field i:I 10: bipush 30 12: putstatic #2 // Field i:I 15: return LineNumberTable: line 9: 0 line 12: 5 line 16: 10 line 17: 15 } SourceFile: "IADDDemo.java"
- 編譯器會按從上到下的順序,收集所有static靜态代碼塊和靜态成員複制的代碼,合并為一個特殊的方法()V;
- 這個新的方法會在類加載的初始化階段被調用,就會按這個方法中從上到下的順序執行。
《init》()V
- 執行個體的構造方法
-
/** * @program: jvmstudy * @description: Demo * @author: SunYang * @create: 2021-07-20 19:33 **/ public class IADDDemo { private String a = "s1"; { b = 20; } private int b = 10; { a = "s2"; } public IADDDemo(String a, int b){ this.a = a; this.b = b; } public static void main(String[] args) { // a = s1 ; b = 20; // a=s2; b=10; // a=s3; b=30 IADDDemo d = new IADDDemo("s3", 30); System.out.println(d.a); // s3 System.out.println(d.b); // 30 } }
-
C:\ideaworkspace\jvmstudy\out\production\jvmstudy>javap -v IADDDemo.class Classfile /C:/ideaworkspace/jvmstudy/out/production/jvmstudy/IADDDemo.class Last modified 2021-7-20; size 783 bytes MD5 checksum 20c07e8c3418d24d32998914eef08c29 Compiled from "IADDDemo.java" public class IADDDemo minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #12.#31 // java/lang/Object."<init>":()V #2 = String #32 // s1 #3 = Fieldref #6.#33 // IADDDemo.a:Ljava/lang/String; #4 = Fieldref #6.#34 // IADDDemo.b:I #5 = String #35 // s2 #6 = Class #36 // IADDDemo #7 = String #37 // s3 #8 = Methodref #6.#38 // IADDDemo."<init>":(Ljava/lang/String;I)V #9 = Fieldref #39.#40 // java/lang/System.out:Ljava/io/PrintStream; #10 = Methodref #41.#42 // java/io/PrintStream.println:(Ljava/lang/String;)V #11 = Methodref #41.#43 // java/io/PrintStream.println:(I)V #12 = Class #44 // java/lang/Object #13 = Utf8 a #14 = Utf8 Ljava/lang/String; #15 = Utf8 b #16 = Utf8 I #17 = Utf8 <init> #18 = Utf8 (Ljava/lang/String;I)V #19 = Utf8 Code #20 = Utf8 LineNumberTable #21 = Utf8 LocalVariableTable #22 = Utf8 this #23 = Utf8 LIADDDemo; #24 = Utf8 main #25 = Utf8 ([Ljava/lang/String;)V #26 = Utf8 args #27 = Utf8 [Ljava/lang/String; #28 = Utf8 d #29 = Utf8 SourceFile #30 = Utf8 IADDDemo.java #31 = NameAndType #17:#45 // "<init>":()V #32 = Utf8 s1 #33 = NameAndType #13:#14 // a:Ljava/lang/String; #34 = NameAndType #15:#16 // b:I #35 = Utf8 s2 #36 = Utf8 IADDDemo #37 = Utf8 s3 #38 = NameAndType #17:#18 // "<init>":(Ljava/lang/String;I)V #39 = Class #46 // java/lang/System #40 = NameAndType #47:#48 // out:Ljava/io/PrintStream; #41 = Class #49 // java/io/PrintStream #42 = NameAndType #50:#51 // println:(Ljava/lang/String;)V #43 = NameAndType #50:#52 // println:(I)V #44 = Utf8 java/lang/Object #45 = Utf8 ()V #46 = Utf8 java/lang/System #47 = Utf8 out #48 = Utf8 Ljava/io/PrintStream; #49 = Utf8 java/io/PrintStream #50 = Utf8 println #51 = Utf8 (Ljava/lang/String;)V #52 = Utf8 (I)V { public IADDDemo(java.lang.String, int); descriptor: (Ljava/lang/String;I)V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=3 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: ldc #2 // String s1 7: putfield #3 // Field a:Ljava/lang/String; 10: aload_0 11: bipush 20 13: putfield #4 // Field b:I 16: aload_0 17: bipush 10 19: putfield #4 // Field b:I 22: aload_0 23: ldc #5 // String s2 25: putfield #3 // Field a:Ljava/lang/String; 28: aload_0 29: aload_1 30: putfield #3 // Field a:Ljava/lang/String; 33: aload_0 34: iload_2 35: putfield #4 // Field b:I 38: return LineNumberTable: line 21: 0 line 9: 4 line 12: 10 line 15: 16 line 18: 22 line 22: 28 line 23: 33 line 24: 38 LocalVariableTable: Start Length Slot Name Signature 0 39 0 this LIADDDemo; 0 39 1 a Ljava/lang/String; 0 39 2 b I public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=2, args_size=1 0: new #6 // class IADDDemo 3: dup 4: ldc #7 // String s3 6: bipush 30 8: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V 11: astore_1 12: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream; 15: aload_1 16: getfield #3 // Field a:Ljava/lang/String; 19: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 22: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream; 25: aload_1 26: getfield #4 // Field b:I 29: invokevirtual #11 // Method java/io/PrintStream.println:(I)V 32: return LineNumberTable: line 28: 0 line 29: 12 line 30: 22 line 31: 32 LocalVariableTable: Start Length Slot Name Signature 0 33 0 args [Ljava/lang/String; 12 21 1 d LIADDDemo; } SourceFile: "IADDDemo.java"
- 編譯器會按從上之下的順序,收集所有{}代碼塊的成員變量指派的代碼,形成新的構造方法,但原始構造方法内的代碼總是在最後。
方法調用
-
/** * @program: jvmstudy * @description: Demo * @author: SunYang * @create: 2021-07-20 21:47 **/ public class MethodsDemo { public MethodsDemo(){} // 構造方法 private void privateMethod(){} // 私有方法 public final void finalMethod(){} // final的方法前面是什麼修飾符都一樣 public void publicMethod(){} // public方法 public static void pubStaticMethod() { System.out.println("靜态"); } // public靜态方法 public static void main(String[] args) { MethodsDemo methodsDemo = new MethodsDemo(); methodsDemo.privateMethod(); methodsDemo.finalMethod(); methodsDemo.publicMethod(); methodsDemo.pubStaticMethod(); MethodsDemo.pubStaticMethod(); } }
-
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: new #5 // class MethodsDemo 3: dup 4: invokespecial #6 // Method "<init>":()V 7: astore_1 8: aload_1 9: invokespecial #7 // Method privateMethod:()V 12: aload_1 13: invokevirtual #8 // Method finalMethod:()V 16: aload_1 17: invokevirtual #9 // Method publicMethod:()V 20: aload_1 21: pop 22: invokestatic #10 // Method pubStaticMethod:()V 25: invokestatic #10 // Method pubStaticMethod:()V 28: return LineNumberTable: line 22: 0 line 23: 8 line 24: 12 line 25: 16 line 26: 20 line 27: 25 line 28: 28 LocalVariableTable: Start Length Slot Name Signature 0 29 0 args [Ljava/lang/String; 8 21 1 methodsDemo LMethodsDemo; } SourceFile: "MethodsDemo.java"
invokespecial invokestatic
- 屬于靜态綁定
- 在位元組碼指令生成的時候就知道如何找到哪個類的哪個方法。
- 構造方法唯一确定
- 私有方法唯一确定
- 靜态方法也是可以唯一确定的
- 構造方法、私有方法、父類方法,這些方法在運作期都是唯一的,不會改變
invokevirtual
- 屬于動态綁定
- public 和 final(待考證為什麼)
- public方法可以被重寫,在編譯期間不能确定自己調用哪個對象的方法,不能确定是父類的還是子類的。就是類似于這種
-
invokevirtual com.Base.func:(I):V invokevirtual com.Child.func:(I):V // 位元組碼指令是根據這個**com.Base.**和**com.Child.**去選擇調用哪個對象的func()方法的,因為這個方法是公共的,是可以被重寫的,他在編譯期的時候,并沒有執行個體化對象,也就是沒有new 對象,那這個時候我們就不知道要調用這個方法的是子類**com.Child.**還是父類com.Base.去調用這個方法,我們隻有執行到這個指令的時候,去找執行個體化的對象,我們才知道是哪個對象調用的它。
- 專業講叫方法的入口()
- https://www.cnblogs.com/holoyong/p/7505908.html 部落格
- 當建立第一個對象時,類中的執行個體方法就配置設定了入口位址,當再建立對象時,則不再配置設定入口位址,也就是說,方法的入口位址被所有的對象共享,當所有的對象都不存在時,方法的入口位址才被取消。對于類中的類方法,在該類被加載到記憶體時,就配置設定了相應的入口位址。進而類方法不僅可以被類建立的任何對象調用執行,也可以直接通過類名調用。
- invokevirtual性能低于invokespecial invokestatic 因為invokevirtual是動态綁定
位元組碼解析
-
0: new #5 // class MethodsDemo // 在堆中建立一個對象,并将引用位址傳回給操作數棧 3: dup // 然後操作數棧會再複制一份對象的引用位址(作為傳給構造器的“this”參數),用來調用init(構造)方法,因為調用完方法,這個引用位址就會被彈出,如果不複制,na 4: invokespecial #6 // Method "<init>":()V // 調用init方法 7: astore_1 // 将引用位址 存儲在局部變量表中 8: aload_1 // 加載引用位址到操作數棧中 9: invokespecial #7 // Method privateMethod:()V // 調用方法 12: aload_1 13: invokevirtual #8 // Method finalMethod:()V 16: aload_1 17: invokevirtual #9 // Method publicMethod:()V 20: aload_1 // 加載對象引用位址到操作數棧中 21: pop // 彈出 , 因為pubStaticMethod:()V是靜态方法,不需要執行個體對象調用,是以彈出 22: invokestatic #10 // Method pubStaticMethod:()V 25: invokestatic #10 // Method pubStaticMethod:()V 28: return
- DUP指令(指派一份引用位址到操作數棧頂)及原因
-
可以看到,new位元組碼指令的作用是建立指定類型的對象執行個體、對其進行預設初始化,并且将指向該執行個體的一個引用壓入操作數棧頂;
然後因為invokespecial會消耗掉操作數棧頂的引用作為傳給構造器的“this”參數,是以如果我們希望在invokespecial調用後在操作數棧頂還維持有一個指向建立對象的引用,就得在invokespecial之前先“複制”一份引用——這就是這個dup的來源。
連結:https://www.zhihu.com/question/52749416/answer/132831920
-
6. 多态的原理
運作代碼
- 停在System.in.red()方法上,這時運作jps擷取程序id
運作HSDB工具
-
java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB
- 進入圖形界面attach程序id
- 底層都是一個c++的strkter結構
- 這是dog執行個體對象
- 這是Dog類(方法區)
- 類中的方法(多态的方法)存在一張Vtable(虛方法表中),靜态方法;私有方法;final方法(final待考證)不在這裡,在方法區
- Vtable和Class類的位址相差1B8就是他的位址
- 先找new dog()對象在堆中的位址,然後找到對象頭的類型指針,類型指針儲存的是Dog.class的位址,然後找到Dog.class 然後根據類資訊中的虛方法表找到可被重寫的方法的入口位址(記憶體位址)
- 所有支援重寫的方法的入口位址
- 因為Animal沒有重寫Object的finalize()方法和equals()方法,是以調用的時候就是Object方法的位址(方法入口)
虛方法表
- 是在類的加載的連結階段就會生成,是以是在類加載的時候确定的每個方法的入口位址。
- 優化,緩存,如果多次調用同一個方法,就會從緩存中拿 ,就不用經曆下面那麼多步驟
- 如果在運作期間,JVM發現隻有一個子類類型,可以做一個單type類型優化
總結
當執行invokevirtual指令時,
- 先通過棧幀中的操作數棧(存在局部變量表中,但是在使用的時候一定是先加載到操作數棧中)中的對象的引用位址找到對象,
- 然後通過分析對象頭中的類型指針,找到對象的實際Class
- 然後再類結構中存有虛方法表,他在類加載的連結階段就已經根據方法的重寫規則生成好了
- 通過查表得到方法的具體位址(方法的入口)
- 執行方法位元組碼
7. 異常處理
try-catch
-
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @program: jvmstudy * @description: Demo * @author: SunYang * @create: 2021-07-22 21:14 **/ public class TryCatchDemo { public static void main(String[] args) throws NoSuchMethodException { try { Method test = TryCatchDemo.class.getMethod("test"); } catch (NoSuchMethodException | IllegalAccessError | InvocationTargetException) { } } }
-
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=3, args_size=1 0: iconst_0 // -1 到 5 用iconst 1: istore_1 2: bipush 10 4: istore_1 5: goto 12 8: astore_2 9: bipush 20 11: istore_1 12: return Exception table: // 異常表 會檢測[2,5) 中的代碼,如果其中出現了異常,就會進行類型比對,和Exception是否一緻或者是否是子類異常,如果一緻就會進入到第8行 from to target type 2 5 8 Class java/lang/Exception LineNumberTable: line 9: 0 line 11: 2 line 14: 5 line 12: 8 line 13: 9 line 15: 12 LocalVariableTable: Start Length Slot Name Signature 9 3 2 e Ljava/lang/Exception; 0 13 0 args [Ljava/lang/String; 2 11 1 i I StackMapTable: number_of_entries = 2 frame_type = 255 /* full_frame */ offset_delta = 8 locals = [ class "[Ljava/lang/String;", int ] stack = [ class java/lang/Exception ] frame_type = 3 /* same */ }
- Exception table:(異常表)
- 多個catch
- 槽位複用: 三個異常共用一個槽位,因為隻能發生一個異常
multi-catch的情況
-
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @program: jvmstudy * @description: Demo * @author: SunYang * @create: 2021-07-22 21:14 **/ public class TryCatchDemo { public static void main(String[] args) throws NoSuchMethodException { try { Method test = TryCatchDemo.class.getMethod("test"); test.invoke(null); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } } public static void test() { System.out.println("ok"); } }
-
public static void main(java.lang.String[]) throws java.lang.NoSuchMethodException; descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=2, args_size=1 0: ldc #2 // class TryCatchDemo 2: ldc #3 // String test 4: iconst_0 5: anewarray #4 // class java/lang/Class 8: invokevirtual #5 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method; 11: astore_1 12: aload_1 13: aconst_null 14: iconst_0 15: anewarray #6 // class java/lang/Object 18: invokevirtual #7 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object; 21: pop 22: goto 30 25: astore_1 26: aload_1 27: invokevirtual #11 // Method java/lang/ReflectiveOperationException.printStackTrace:()V 30: return Exception table: from to target type 0 22 25 Class java/lang/NoSuchMethodException // 三個異常的方法入口一樣 0 22 25 Class java/lang/IllegalAccessException 0 22 25 Class java/lang/reflect/InvocationTargetException LineNumberTable: line 13: 0 line 14: 12 line 17: 22 line 15: 25 line 16: 26 line 18: 30 LocalVariableTable: Start Length Slot Name Signature 12 10 1 test Ljava/lang/reflect/Method; 26 4 1 e Ljava/lang/ReflectiveOperationException; 0 31 0 args [Ljava/lang/String; StackMapTable: number_of_entries = 2 frame_type = 89 /* same_locals_1_stack_item */ stack = [ class java/lang/ReflectiveOperationException ] frame_type = 4 /* same */ Exceptions: throws java.lang.NoSuchMethodException public static void test(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=0, args_size=0 0: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #13 // String ok 5: invokevirtual #14 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 20: 0 line 21: 8 } SourceFile: "TryCatchDemo.java"
finally
-
public static void main(java.lang.String[]) throws java.lang.NoSuchMethodException; descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=4, args_size=1 0: iconst_0 1: istore_1 // 0 -> i 2: bipush 10 // try ----------------------------------------------- 4: istore_1 // 10 -> i | 5: bipush 30 // finally | 7: istore_1 // 30 -> i | 8: goto 27 // return -------------------------------------------- 11: astore_2 // catch Exception -> e ------------------------------- 12: bipush 20 // | 14: istore_1 // 20 - i | 15: bipush 30 // finally | 17: istore_1 // 30 -> i | 18: goto 27 // return --------------------------------------------- 21: astore_3 // catch any -> slot 3 -------------------------------- 22: bipush 30 // finally | 24: istore_1 // 30 -> i | 25: aload_3 // <- slot 3 | 26: athrow // throw ---------------------------------------------- 27: return Exception table: from to target type 2 5 11 Class java/lang/Exception 2 5 21 any // 剩餘的異常類型,比如Error 11 15 21 any // 剩餘的異常類型,比如Error LineNumberTable: line 12: 0 line 14: 2 line 18: 5 line 19: 8 line 15: 11 line 16: 12 line 18: 15 line 19: 18 line 18: 21 line 19: 25 line 20: 27 LocalVariableTable: Start Length Slot Name Signature 12 3 2 e Ljava/lang/Exception; 0 28 0 args [Ljava/lang/String; 2 26 1 i I StackMapTable: number_of_entries = 3 frame_type = 255 /* full_frame */ offset_delta = 11 locals = [ class "[Ljava/lang/String;", int ] stack = [ class java/lang/Exception ] frame_type = 73 /* same_locals_1_stack_item */ stack = [ class java/lang/Throwable ] frame_type = 5 /* same */ Exceptions: throws java.lang.NoSuchMethodException }
- 可以看到 finally中的代碼被複制了3份,分别放入try流程,catch流程以及catch剩餘的異常類型流程,就是finally為什麼永遠會被執行。
finally出現了return
-
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @program: jvmstudy * @description: Demo * @author: SunYang * @create: 2021-07-22 21:14 **/ public class TryCatchDemo { public static void main(String[] args) throws NoSuchMethodException { System.out.println(test()); } public static int test() { try { return 10; } finally { return 20; } } }
- finally中如果出現了return 會吞掉異常
finally對傳回值的影響
-
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @program: jvmstudy * @description: Demo * @author: SunYang * @create: 2021-07-22 21:14 **/ public class TryCatchDemo { public static void main(String[] args) throws NoSuchMethodException { System.out.println(test()); } public static int test() { int i = 10; try { return i; } finally { i = 20; } } }
-
public static void main(java.lang.String[]) throws java.lang.NoSuchMethodException; descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: invokestatic #3 // Method test:()I 6: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 9: return LineNumberTable: line 13: 0 line 15: 9 LocalVariableTable: Start Length Slot Name Signature 0 10 0 args [Ljava/lang/String; Exceptions: throws java.lang.NoSuchMethodException public static int test(); descriptor: ()I flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=3, args_size=0 // 這裡的locals等于三是因為 一個無名要固定傳回值,一個要athrow抛出異常用 0: bipush 10 // 2: istore_0 // 10 -> i slot 0 3: iload_0 // < - i(10) 4: istore_1 // 10 -> slot 1 (無名槽位) 暫存至slot 1,目的是為了固定傳回值 5: bipush 20 // 20 放入棧頂 7: istore_0 // 20 -> i 8: iload_1 // <- slot 1(10) 載入slot 1 暫存的值 放入棧頂 9: ireturn // 傳回棧頂的int(10) 10: astore_2 11: bipush 20 13: istore_0 14: aload_2 15: athrow Exception table: from to target type 3 5 10 any LineNumberTable: line 17: 0 line 19: 3 line 21: 5 line 19: 8 line 21: 10 line 22: 14 LocalVariableTable: Start Length Slot Name Signature 3 13 0 i I StackMapTable: number_of_entries = 1 frame_type = 255 /* full_frame */ offset_delta = 10 locals = [ int ] stack = [ class java/lang/Throwable ] } SourceFile: "TryCatchDemo.java"
8. synchronized
- 當synchronized中的代碼塊出現問題,怎麼正确解鎖操作?
/** * @program: jvmstudy * @description: Demo * @author: SunYang * @create: 2021-07-22 22:40 **/ public class SynchronizedDemo { public static void main(String[] args) { Object lock = new Object(); synchronized (lock) { System.out.println("ok"); } } }
-
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=4, args_size=1 0: new #2 // class java/lang/Object 3: dup // 複制一份位址的引用放在操作數棧 4: invokespecial #1 // Method java/lang/Object."<init>":()V 初始化方法 消耗掉一份棧頂的位址引用 7: astore_1 // 存儲到局部變量表 1的位置 8: aload_1 // 将1槽位的加載到操作數棧 9: dup // 複制一份再操作數棧頂 10: astore_2 // 放到無名的局部變量表中2的位置 用來解鎖操作 11: monitorenter // monitorenter(lock引用) 鎖 12: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 15: ldc #4 // String ok 17: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V // 消耗掉棧頂的唯一一份引用位址 20: aload_2 // 将lock位址引用加載到操作數棧頂 21: monitorexit // 解鎖lock(引用位址) 22: goto 30 25: astore_3 // 将異常的對象引用存到槽位3 26: aload_2 // 加載存到無名2号槽位的lock位址引用 27: monitorexit // 解鎖lock(引用位址) 28: aload_3 // 加載3号槽位異常對象位址引用 29: athrow // 抛出 30: return Exception table: from to target type 12 22 25 any // 如果出現異常 跳到25 25 28 25 any LineNumberTable: line 10: 0 line 11: 8 line 12: 12 line 13: 20 line 15: 30 LocalVariableTable: Start Length Slot Name Signature 0 31 0 args [Ljava/lang/String; 8 23 1 lock Ljava/lang/Object; StackMapTable: number_of_entries = 2 frame_type = 255 /* full_frame */ offset_delta = 25 locals = [ class "[Ljava/lang/String;", class java/lang/Object, class java/lang/Object ] stack = [ class java/lang/Throwable ] frame_type = 250 /* chop */ offset_delta = 4 }
5: ldc #4 // String ok
17: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V // 消耗掉棧頂的唯一一份引用位址
20: aload_2 // 将lock位址引用加載到操作數棧頂
21: monitorexit // 解鎖lock(引用位址)
22: goto 30
25: astore_3 // 将異常的對象引用存到槽位3
26: aload_2 // 加載存到無名2号槽位的lock位址引用
27: monitorexit // 解鎖lock(引用位址)
28: aload_3 // 加載3号槽位異常對象位址引用
29: athrow // 抛出
30: return
Exception table:
from to target type
12 22 25 any // 如果出現異常 跳到25
25 28 25 any
LineNumberTable:
line 10: 0
line 11: 8
line 12: 12
line 13: 20
line 15: 30
LocalVariableTable:
Start Length Slot Name Signature
0 31 0 args [Ljava/lang/String;
8 23 1 lock Ljava/lang/Object;
StackMapTable: number_of_entries = 2
frame_type = 255
offset_delta = 4
}
-