黑馬JVM解析筆記(五):深入理解Java字節碼執行機制

1.從字節碼的角度分析i++

/**
* 從字節碼角度分析 a++ 相關題目
*/
public class Demo3_2 {public static void main(String[] args) {int a = 10;int b = a++ + ++a + a--;System.out.println(a);System.out.println(b);}
}

a++++a 實際上代表了兩個不同的操作,它們分別對應自增和取數的過程。首先需要明確的是,自增操作是在局部變量上完成的。接著,了解這兩者的區別至關重要:哪一個是先取數據,哪一個是先進行自增(或自減)。

具體來說:

  • a++a-- 先取數,再進行局部自增(或自減)。
  • ++a--a 先進行局部自增(或自減),然后再取數。

因此,上述的代碼最終結果是 b = 34

2.條件判斷指令

在這里插入圖片描述

2.1 幾點說明:
  • byte,short,char 都會按 int 比較,因為操作數棧都是 4 字節
  • goto 用來進行跳轉到指定行號的字節碼
2.2 案例
public class Demo3_3 {public static void main(String[] args) {int a = 0;if(a == 0) {a = 10;} else {a = 20;}
}//字節碼
0: iconst_0
1: istore_1
2: iload_1
3: ifne 12  //判斷是否為0,若是跳到12行
6: bipush 10
8: istore_1
9: goto 15  //跳轉
12: bipush 20
14: istore_1
15: return

3. 循環控制指令

好的,下面是經過整理和潤色后的內容:

在 Java 中,循環控制結構其實是通過之前介紹的字節碼指令來實現的。我們通過分析 whiledo whilefor 循環的字節碼,可以更好地理解它們背后的實現原理。

3.1. while 循環字節碼示例

對于以下的 while 循環代碼:

public class Demo3_4 {public static void main(String[] args) {int a = 0;while (a < 10) {a++;}}
}

對應的字節碼是:

0: iconst_0
1: istore_1
2: iload_1
3: bipush 10
5: if_icmpge 14
8: iinc 1, 1
11: goto 2
14: return

字節碼的執行流程如下:

  • 初始化變量 a(即 istore_1)。
  • 每次循環時,檢查 a 是否小于 10(通過 if_icmpge 判斷)。
  • 如果條件為 true,則執行 iinc(自增 a),然后跳回循環判斷(goto 2)。
  • 如果條件為 false,則退出循環并結束程序。
3.2 do while 循環字節碼示例

對于以下的 do while 循環代碼:

public class Demo3_5 {public static void main(String[] args) {int a = 0;do {a++;} while (a < 10);}
}

對應的字節碼是:

0: iconst_0
1: istore_1
2: iinc 1, 1
5: iload_1
6: bipush 10
8: if_icmplt 2
11: return

字節碼的執行流程如下:

  • 初始化變量 aistore_1)。
  • 在循環內部先執行 iinc(自增 a),然后檢查 a 是否小于 10。
  • 如果 a 小于 10,跳回到步驟 2,繼續自增并檢查條件。
  • 如果條件不滿足,則退出循環并結束程序。
3.3 for 循環字節碼示例

最后是 for 循環。其實,如果我們仔細觀察 whilefor 循環的字節碼,我們會發現它們是一模一樣的。盡管語法上 for 循環包括了初始化、條件判斷和自增部分,但在字節碼層面,它們表現得幾乎是相同的。

因此,以下是 for 循環的字節碼:

public class Demo3_6 {public static void main(String[] args) {for (int a = 0; a < 10; a++) {// do something}}
}

字節碼也是與 while 循環的字節碼完全相同:

0: iconst_0
1: istore_1
2: iload_1
3: bipush 10
5: if_icmpge 14
8: iinc 1, 1
11: goto 2
14: return

4.判斷的結果

請從字節碼角度分析,下列代碼運行的結果:

public class Demo3_6_1 {public static void main(String[] args) {int i = 0;int x = 0;while (i < 10) {x = x++;i++;}System.out.println(x); // 結果是 0}
}

分析結果為什么是0,是因為x++,先取數后自增,取數到操作棧依舊是0,局部變量位置是1,但是x=,這個表示復制,又把局部變量的位置改成0,這樣就陷入死循環,無論循環多少次一直為0.

5.構造方法

5.1 <cint> ()v
public class StaticTest {static int i = 10;static {i = 30;}static {i = 20;}public static void main(String[] args) {}
}

編譯器會按從上至下的順序,收集所有靜態代碼塊和靜態成員賦值的代碼,合并為一個特殊的方法

 Code:0: bipush        102: putstatic     #7                  // Field i:I5: bipush        307: putstatic     #7                  // Field i:I10: bipush        2012: putstatic     #7                  // Field i:I15: return
5.2 <int>()V
public class Demo3_8_2 {private String a = "s1";{b = 20;}private int b = 10;{a = "s2";}public Demo3_8_2(String a, int b) {this.a = a;this.b = b;}public static void main(String[] args) {Demo3_8_2 d = new Demo3_8_2("s3", 30);System.out.println(d.a);System.out.println(d.b);}
}

編譯器會按從至下的順序,收集所有{}代碼和成員變量,形成新的構造方法,但是原始的構造放內的代碼執行總是在最后面

因此這個代碼的結果是:s3和30

6.方法的調用

public class Demo3_9 {
public Demo3_9() { }private void test1() { }private final void test2() { }public void test3() { }public static void test4() { }public static void main(String[] args) {Demo3_9 d = new Demo3_9();d.test1();d.test2();d.test3();d.test4();Demo3_9.test4();}
}

字節碼

0: new #2 // class cn/itcast/jvm/t3/bytecode/Demo3_9
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokespecial #4 // Method test1:()V
12: aload_1
13: invokespecial #5 // Method test2:()V
16: aload_1
17: invokevirtual #6 // Method test3:()V
20: aload_1
21: pop
22: invokestatic #7 // Method test4:()V
25: invokestatic #7 // Method test4:()V
28: return

幾種類型的方法的調用說明一下:

  • invokespecial,普通私有方法的調用
  • invokevirtual,調用實例方法,通過對象的類型決定調用哪個方法(普通方法調用)。
  • invokestatic,靜態的方法調用

整個流程為:

  • new 新建對象,給對象分配堆內存,然后把對象引用壓入操作數棧
  • dup,是把對象復制一份,為什么要復制一份呢,一個是調用對象構造方法,一個是把這個對象賦給局部變量
  • 然后調用方法就行
  • 備注:d.test3();這種形式調用靜態方法,會有一個pop出棧的操作,這樣就會把【對象引用】從操作數彈掉

7.多態是在字節碼中是如何調用的

當執行 invokevirtual 指令時,

  1. 獲取對象引用:從棧幀中的操作數棧中獲取方法調用的對象引用。
  2. 分析對象頭:通過對象引用找到對象的 Class 對象,進一步分析該對象的實際類型。
  3. 查找 vtable:通過對象的實際類型,獲取其 vtable(虛方法表)。
  4. 查表得到方法地址:根據方法名和簽名,在 vtable 中查找到對應的方法地址,確保方法是動態綁定的。
  5. 執行方法字節碼:跳轉到方法的字節碼地址并執行該方法。

8.異常處理

8.1 catch
public class Demo3_11_1 {
public static void main(String[] args) {int i = 0;try {i = 10;} catch (Exception e) {i = 20;}}
}//main方法的字節碼
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: istore_1
2: bipush 10
4: istore_1
5: goto 12
8: astore_2
9: bipush 20
11: istore_1
12: return
Exception table:
from to target type
2 5 8 Class java/lang/Exception
LineNumberTable: ...
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: ...
MethodParameters: ...
}
  • 可以看到多出來一個 Exception table 的結構,[from, to) 是前閉后開的檢測范圍,一旦這個范圍內的字節碼執行出現異常,則通過 type 匹配異常類型,如果一致,進入 target 所指示行號
  • 8 行的字節碼指令 astore_2 是將異常對象引用存入局部變量表的 slot 2 位置
8.2 finally
public class Demo3_11_4 {
public static void main(String[] args) {int i = 0;try {i = 10;} catch (Exception e) {i = 20;} finally {i = 30;}}
}
public static void main(java.lang.String[]);
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 Exceptin -> 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: ...
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: ...
MethodParameters: ...

由于finally必須要執行,可以看到 finally 中的代碼被復制了 3 份,分別放入 try 流程,catch 流程以及 catch 剩余的異常類型流程,最后一份是,怕catch捕獲不到的異常,執行不了所有在其他異常也復制了一份

8.2.1finally面試題1
public class Demo3_12_2 {
public static void main(String[] args) {int result = test();System.out.println(result);public static int test() {try {return 10;} finally {return 20;}}
}//方法字節碼
public static int test();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=0
0: bipush 10 // <- 10 放入棧頂
2: istore_0 // 10 -> slot 0 (從棧頂移除了)
3: bipush 20 // <- 20 放入棧頂
5: ireturn // 返回棧頂 int(20)
6: astore_1 // catch any -> slot 1
7: bipush 20 // <- 20 放入棧頂
9: ireturn // 返回棧頂 int(20)
Exception table:
from to target type
0 3 6 any
LineNumberTable: ...
StackMapTable: ...

由這個字節碼我們可以看到,finally里面的邏輯一定會執行,但是如果 finally 中有 return,它會覆蓋任何異常或者其他的返回值。

8.2.2 finally面試題2
public class Demo3_12_2 {public static void main(String[] args) {int result = test();System.out.println(result);}public static int test() {int i = 10;try {return i;} finally {i = 20;}}
}
//字節碼
public static int test();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=0
0: bipush 10 // <- 10 放入棧頂
2: istore_0 // 10 -> i
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: ...
LocalVariableTable:
Start Length Slot Name Signature
3 13 0 i I

通過使用 istore_1 暫存 i 的值,字節碼確保了即使在 finally 塊中修改了 i 的值(將其設置為 20局部變量),最終返回的仍然是 try 塊中的原始 i 值(即 10)。

8.2.2 finally面試題2
public class Demo3_12_2 {public static void main(String[] args) {int result = test();System.out.println(result);}public static int test() {int i = 10;try {return i;} finally {i = 20;}}
}
//字節碼
public static int test();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=0
0: bipush 10 // <- 10 放入棧頂
2: istore_0 // 10 -> i
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: ...
LocalVariableTable:
Start Length Slot Name Signature
3 13 0 i I

istore_1,可以把值固定住

9.synchronnized

public class Demo3_13 {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 // new Object
3: dup
4: invokespecial #1 // invokespecial <init>:()V
7: astore_1 // lock引用 -> lock
8: aload_1 // <- lock (synchronized開始)
9: dup
10: astore_2 // lock引用 -> slot 2
11: monitorenter // monitorenter(lock引用)
12: getstatic #3 // <- System.out
15: ldc #4 // <- "ok"
17: invokevirtual #5 // invokevirtual println:
(Ljava/lang/String;)V
20: aload_2 // <- slot 2(lock引用)
21: monitorexit // monitorexit(lock引用)
22: goto 30
25: astore_3 // any -> slot 3
26: aload_2 // <- slot 2(lock引用)
27: monitorexit // monitorexit(lock引用)
28: aload_3
29: athrow
30: return
Exception table:
from to target type
12 22 25 any
25 28 25 any
LineNumberTable: ...
LocalVariableTable:
Start Length Slot Name Signature
0 31 0 args [Ljava/lang/String;
8 23 1 lock Ljava/lang/Object;
StackMapTable: ...
MethodParameters: ...

幾點說明:

  • 為什么字節碼第9行要把鎖的引用復制兩份,因為是需要給兩個操作,一個加鎖,一個解鎖
  • 還有就是異常捕獲,由于對程序進行加鎖,如果在執行程序的過程發生什么異常,導致程序沒有解鎖,這樣就會程序就會出問題。因此要設置異常捕獲,如果出現異常,就還是要進行解鎖,再拋出異常
  • 方法級別的 synchronized 不會在字節碼指令中有所體現

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/912128.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/912128.shtml
英文地址,請注明出處:http://en.pswp.cn/news/912128.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

從社交媒體到金融“超級應用”,馬斯克X平臺將上線投資交易服務

報道稱&#xff0c;馬斯克旗下的X平臺將推出“超級App”&#xff0c;提供投資和交易服務&#xff0c;另外&#xff0c;X也在探索引入信用卡或借記卡。作為金融服務布局的第一步&#xff0c;X平臺已宣布將推出X Money——一項數字錢包和點對點支付服務&#xff0c;Visa將成為其首…

【入門第2課】Splunk數據接入

前言 Splunk支持多種多樣的數據源,比如它可以直接上傳文件,可以監控本地的任何目錄或文件,也可以配置通用轉發器等方式來完成數據接入。Splunk所有的設置都可以通過Web頁面、使用Splunk CLI命令,甚至是直接修改配置文件,以此來完成設置。 那么,如何接入數據呢?我們通過…

【數據挖掘】關聯規則算法學習—Apriori

關聯規則算法學習—Apriori Apriori算法是關聯規則挖掘中的經典算法&#xff0c;用于發現數據集中的頻繁項集和強關聯規則。其核心思想基于先驗性質&#xff1a;若一個項集是頻繁的&#xff0c;則其所有子集也一定是頻繁的。該算法通過逐層搜索的迭代方法高效挖掘關聯規則。 要…

助力高考,利用python獲取本專科專業選考科目要求

大家好&#xff0c;今天我們來利用python技術助力高考&#xff0c;獲取網上的本專科專業選考科目要求&#xff0c;用到的Python模塊有&#xff1a;Requests、Json、BeautifulSoup、Pandas &#xff0c;主要思路是Requests獲取接口請求&#xff0c;利用BeautifulSoup 解析網站的…

Python 商務數據分析—— NumPy 學習筆記Ⅱ

一、 數組元素值的替換 我們可以使用索引或 where () 函數來替換 NumPy 數組中的元素值。 1.1 方式一&#xff1a;索引 import numpy as npnp.random.seed(42)a3 np.random.randint(0, 10, size(3, 4))print("原數組:\n", a3)a3\[1] 0 # 將a3數組第一行數據全…

遙感圖像語義分割1-安裝mmsegmentation

參考&#xff1a; mmsegmentation: 安裝并使用自定義數據集進行訓練_mmsegmentation安裝-CSDN博客 最新Windows配置安裝mmcv與mmsegmentation&#xff0c;以及mmsegmentation的驗證_mmcv安裝-CSDN博客 GitCode - 全球開發者的開源社區,開源代碼托管平臺 參考&#xff1a; …

【菜狗的記錄】模糊聚類最大樹、圖神經網絡、大模型量化——20250627

每日學習過程中記錄的筆記&#xff0c;從各個網站整理下來&#xff0c;用于當日復盤。 如果其中的知識點能幫到你&#xff0c;也很榮幸呀。 -------------------------------------------------------20250622------------------------------------------------------------- …

《短劇平臺開發指南:技術方案、核心功能與行業趨勢》

一、短劇行業現狀與系統開發價值 近年來&#xff0c;短劇市場呈現爆發式增長&#xff0c;成為數字內容領域的新風口。數據顯示&#xff0c;2023年國內短劇市場規模已突破300億元&#xff0c;用戶規模達到4.5億。這種以"短、平、快"為特點的內容形式&#xff0c;憑借…

[FPGA]嵌入式系統FPGA設計資源

嵌入式系統FPGA設計資源 一、供應商 https://www.altera.com- Altera FPGA 供應商網站 https://www.altera.com/events/northamerica/intel-soc-fpga-developer-forum/overview.html- SoC 開發人員論壇 https://www.altera.com/content/dam/altera-www/global/en_US/pdfs/li…

ClickHouse 可觀測性最佳實踐

ClickHouse 介紹 ClickHouse 是一款高性能、列式存儲的開源分析型數據庫&#xff0c;專為在線分析處理&#xff08;OLAP&#xff09;場景設計。它能夠處理海量數據&#xff0c;支持實時查詢和復雜的數據分析&#xff0c;具備極高的讀寫性能和數據壓縮能力。ClickHouse 提供了強…

Android Framework設置時間為24小時制

文章目錄 定位源碼實現附錄12 小時制與 24 小時制的詳細解析一、基本定義與核心區別二、轉換方法與示例三、應用場景與文化差異四、延伸知識&#xff1a;特殊計時制與歷史背景 目的是把設置中使用默認語言區域關掉&#xff0c;并把使用24小時制打開 如下圖為原始的&#xff1a;…

基于STM32設計的掃地機器人

一、前言 1.1 項目介紹 【1】項目開發背景 隨著社會節奏的加快和人們生活方式的改變&#xff0c;智能家居產品逐漸走入千家萬戶。作為智能清潔系統的重要組成部分&#xff0c;掃地機器人憑借其自動化、高效性和便捷性&#xff0c;成為現代家庭中不可或缺的智能設備之一。傳統…

什么是接口測試?

2025最新Jmeter接口測試從入門到精通&#xff08;全套項目實戰教程&#xff09; 接口測試概念 接口測試是項目測試的一部分&#xff0c;它測試的主要對象是接口&#xff0c;是測試系統組件間接口的一種測試。接口測試主要用于檢測外部系統與所測系統之間以及內部各系統之間的交…

JDY-23藍牙模塊與電腦的連接方式

JDY-23藍牙模塊支持多種連接方式&#xff0c;包括SPP&#xff08;串口通信&#xff09;模式和BLE&#xff08;低功耗藍牙&#xff09;模式。以下是與電腦連接的具體方法&#xff1a; 1. 通過SPP模式連接 JDY-23模塊支持SPP協議&#xff0c;可以通過串口與電腦通信。以下是連接…

【網絡】Linux 內核優化實戰 - net.core.rmem_max

目錄 參數作用與原理默認值與查看方法調整場景與方法適用場景調整方法 與其他參數的協同性能影響與注意事項典型案例總結 net.core.rmem_max 是 Linux 內核中控制 套接字接收緩沖區&#xff08;Receive Buffer&#xff09;最大允許值 的參數。它與 net.core.rmem_default&#…

設計模式 | 工廠模式

工廠模式&#xff08;Factory Pattern&#xff09; 是創建型設計模式的核心成員&#xff0c;它通過將對象創建的邏輯封裝起來&#xff0c;實現了創建與使用的解耦。本文將深入探討工廠模式的核心思想、實現技巧以及在C中的高效實現方式。 為什么需要工廠模式&#xff1f; 在軟…

數字孿生技術驅動UI前端變革:從靜態展示到動態交互的飛躍

hello寶子們...我們是艾斯視覺擅長ui設計、前端開發、數字孿生、大數據、三維建模、三維動畫10年經驗!希望我的分享能幫助到您!如需幫助可以評論關注私信我們一起探討!致敬感謝感恩! 在數字化轉型的深水區&#xff0c;數字孿生技術正以破竹之勢重構 UI 前端的技術邏輯與設計理念…

Django實戰:自定義中間件實現全鏈路操作日志記錄

文章目錄 一、中間件介紹激活中間件生命周期 二、自定義中間件中間件鉤子函數基于類的中間件 三、實戰案例操作日志功能參考資料 一、中間件 介紹 在 Django 中&#xff0c;中間件&#xff08;Middleware&#xff09;是一組輕量級、底層的插件系統&#xff0c;用于全局地改變…

Java編程之迭代器模式(Iterator Pattern)

引言&#xff1a; 走進圖書館&#xff0c;你站在一排書架前&#xff0c;想要瀏覽書籍。你會一格格地從左到右翻閱書籍&#xff0c;而不是去研究書架是什么。 一本書一本書地翻&#xff0c;才知道書架上藏了什么書&#xff0c;研究書架的構造是不知道書籍的內容的。 這種“逐本…

ARM64 linux系統的一般執行過程

1、正在運行的用戶進程X 2、發生異常&#xff08;包括系統調用等&#xff09;&#xff0c;CPU完成的工作&#xff1a;把當前程序指針寄存器PC放入ELR_EL1寄存器&#xff0c;把PSTATE放入SPSR_EL1寄存器&#xff0c;把異常產生的原因放在ESR_EL1寄存器&#xff0c;將異常向量表…