黑马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.tpcf.cn/news/912128.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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;将异常向量表…