JVM字节码与类加载——字节码指令集与解析
文章目录
- 1、概述
- 1.1、字节码与数据类型
- 1.2、指令分类
- 2、加载与存储指令
- 2.1、局部变量入栈指令
- 2.2、常量入栈指令
- 2.3、出栈装入局部变量表指令
- 3、算术指令
- 3.1、彻底理解i++与++i
- 3.2、比较指令
- 4、类型转换指令
- 4.1、宽化类型转换
- 4.2、窄化类型转换
- 5、对象、数组的创建与访问指令
- 5.1、创建指令
- 5.2、字段访问指令
- 5.3、数组操作指令
- 5.4、类型检查指令
- 6、方法调用与返回指令
- 6.1、方法调用指令
- 6.2、方法返回指令
- 7、操作数栈管理指令
- 8、控制转移指令
- 8.1、条件跳转指令
- 8.2、比较条件跳转指令
- 8.3、多条件分支跳转
- 8.4、无条件跳转
- 9、异常处理指令
- 9.1、抛出异常指令
- 9.2、异常处理和异常表
- 10、同步控制指令
- 10.1、方法内指定指令序列的同步
- 10.2、方法级的同步
- 11、小结
字节码指令就是JVM层面对于Java语法逻辑的底层实现,比如在程序中使用的“if else”语句,对应JVM中的指令就是控制转移指令。JVM中还有很多其他类型的字节码指令,比如控制转移指令、异常处理指令等。本贴将详细介绍JVM的不同用途的字节码指令,可以更好地帮助理解Java中的语法逻辑在JVM中是如何实现的。
1、概述
Java字节码对于虚拟机,就好像汇编语言对于计算机,属于基本执行指令。JVM字节码指令由一个字节长度的、代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)而构成。由于JVM是基于栈结构而不是寄存器的结构,所以大多数的指令都不包含操作数,只有一个操作码。JVM操作码的长度为一个字节(即0~255),这意味着指令集的操作码总数不可能超过256条。
不考虑异常处理的情况下,JVM解释器的执行模型可以使用下面的伪代码表示。
掌握常见的字节码指令可以帮助更方便地阅读class文件。
1.1、字节码与数据类型
在JVM的指令集中,大多数的指令都包含了其操作所对应的数据类型信息,例如iload指令用于从局部变量表中加载int型的数据到操作数栈中,fload指令中的f表示加载float类型的数据。对于大部分与数据类型相关的字节码指令,它们的操作码助记符中都有特殊的字符来表明专门为哪种数据类型服务,如下表所示:
也有一些指令的助记符中没有明确地指明操作类型的字母,如arraylength指令,它没有代表数据类型的特殊字符,但操作数永远只能是一个数组类型的对象。还有另外一些指令,如无条件跳转指令goto则是与数据类型无关的。
由于JVM的操作码长度只有一个字节,所以包含了数据类型的操作码就为指令集的设计带来了很大的压力。如果每一种与数据类型相关的指令都支持JVM所有运行时数据类型的话,那指令的数量就会超出一个字节所能表示的数量范围了。
字节码指令集中大部分的指令都不支持byte、char和short,甚至没有任何指令支持boolean类型,例如,load指令有操作int类型的iload,但是没有操作byte类型的同类指令。编译器会在编译期或运行期将byte和short类型的数据带符号扩展(Sign-Extend)为相应的int类型数据,将boolean和char类型数据零位扩展(Zero-Extend)为相应的int类型数据。与之类似,在处理boolean、byte、short和char类型的数组时,也会转换为使用对应的int类型的字节码指令来处理。因此,大多数对于boolean、byte、short和char类型数据的操作,实际上都是使用相应的int类型作为运算类型。在编写Java代码时也有所体现,比如Java允许byte b1 = 12,short s1 = 10,而b1 + s1的结果至少要使用int类型来接收等语法。
1.2、指令分类
由于完全介绍和学习这些指令需要花费大量时间。为了让大家能够更快地熟悉和了解这些基本指令,这里将JVM中的字节码指令集按用途大致分成9类,如下表所示:
在做值相关操作时,指令可以从局部变量表、常量池、堆中对象、方法调用、系统调用等区域取得数据,这些数据(可能是值,可能是对象的引用)被压入操作数栈。也可以从操作数栈中取出一个到多个值(pop多次),完成赋值、加减乘除、方法传参、系统调用等操作。下面分别讲解各种指令的详细含义。
2、加载与存储指令
加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。
(1)局部变量入栈指令表示将一个局部变量加载到操作数栈,指令如下:
xload、xload_<n>(其中x为i、l、f、d、a,分别表示int类型,long类型,float类型,
double类型,引用类型;n为数值0到3)
(2)常量入栈指令表示将一个常量加载到操作数栈,指令如下:
(3)出栈装入局部变量表指令表示将一个数值从操作数栈存储到局部变量表,指令如下:
xstore、xstore_<n>(其中x为i、l、f、d、a,分别表示int类型,long类型,float类型,
double类型,引用类型;n为0到3)
(4)扩充局部变量表的访问索引的指令如下:
wide
上面所列举的指令助记符中,有一部分是以尖括号结尾的(例如iload_),这些指令助记符实际上代表了一组指令(例如iload_代表了iload_0、iload_1、iload_2和iload_3这几个指令)。这几组指令都是某个带有一个操作数的通用指令(例如iload)的特殊形式,对于这若干组特殊指令来说,它们表面上没有操作数,不需要进行取操作数的动作,但操作数都隐含在指令中。具体含义如下表所示:
除此之外,它们的语义与原生的通用指令完全一致(例如iload_0的语义与操作数为0时的iload指令语义完全一致)。在尖括号之间的字母指定了指令隐含操作数的数据类型,代表非负的整数,代表int类型数据,代表long类型,代表float类型,代表double类型。
2.1、局部变量入栈指令
局部变量入栈指令将给定的局部变量表中的数据压入操作数栈。这类指令大体可以分为以下两种类型:
xload_<n>(x为i、l、f、d、a,n为 0 到 3)xload(x为i、l、f、d、a)
在这里,x的取值表示数据类型。指令xload_n表示将局部变量表中索引为n的位置中的数据压入操作数栈,比如iload_0、fload_0、aload_0等指令。其中aload_n表示将一个对象引用入栈。指令xload通过指定参数的形式,把局部变量压入操作数栈,当使用这个命令时,表示局部变量的数量可能超过了4个,比如指令iload、fload等。
2.2、常量入栈指令
常量入栈指令主要负责将常量加载到操作数栈,根据常量的数据类型和入栈内容的不同,又可以分为const系列、push系列和ldc系列指令。
2.3、出栈装入局部变量表指令
出栈装入局部变量表指令用于将一个数值从操作数栈存储到局部变量表的指定位置。这类指令主要以store的形式存在,整体可以分为三类,分别是xstore(x为i、l、f、d、a)、xstore_n(x为i、l、f、d、a,n为0至3)和xastore(其中x为i、l、f、d、a、b、c、s)。xstore和xstore_n类型的指令负责对基本数据类型和引用数据类型的操作,xastore类型的指令主要负责数组的操作。
一般说来,出栈装入局部变量表指令需要接收一个参数,用来指明将弹出的元素放在局部变量表的第几个位置。但是,由于局部变量表前几个位置使用非常频繁,为了尽可能压缩指令大小,使用专门的istore_1指令表示将弹出的元素放置在局部变量表索引为1的位置。类似的还有istore_0、istore_2、istore_3,它们分别表示从操作数栈顶弹出一个元素,存放在局部变量表索引为0、2、3的位置。这种做法虽然增加了指令数量,但是可以大大压缩生成的字节码的体积。如果局部变量表很大,需要存储的槽位大于3,那么可以使用xstore指令,外加一个参数,用来表示需要存放的槽位位置。
3、算术指令
算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新压入操作数栈。基本运算包括加法、减法、乘法、除法、取余、取反、自增等。
算术指令如下表所示:
每一类指令也支持多种数据类型,例如add指令就包括iadd、ladd、fadd和dadd四种,分别支持int类型、long类型、float类型和double类型。
所有运算指令中,都没有直接支持byte、short、char和boolean类型的指令,对于这些数据的运算,都使用int类型的指令来处理。此外,在处理boolean、byte、short和char类型的数组时,也会转换为对应的int类型的字节码指令来处理。JVM中的实际数据类型与运算类型的对应关系如下表所示:
数据运算可能会导致溢出,例如两个很大的正整数相加,结果可能是一个负数。其实JVM规范并无明确规定过整型数据溢出的具体结果,仅规定了在处理整型数据时,只有除法指令以及求余指令中当出现除数为0时会导致虚拟机抛出异常“ArithmeticException”。当一个操作产生溢出时,将会使用有符号的无穷大表示,如果某个操作结果没有明确的数学定义的话,将会使用NaN值来表示。所有使用NaN值作为操作数的算术操作,结果都会返回NaN。在数据运算过程中,所有的运算结果都必须舍入到适当的精度,比如要求保留3位小数,那么就需要丢弃多余的数位,常见的运算模式包括向最接近数舍入模式和向零舍入模式。
3.1、彻底理解i++与++i
大家都知道i++与++i都是对自身进行加1操作,但是它们之间的区别到底是什么呢?我们今天从字节码指令的角度来理解i++和++i,具体代码如代码清单如下所示:
方法method1( )对应的字节码指令如下:
0 bipush 102 istore_13 iinc 1 by 16 return
方法method2( )对应的字节码指令如下:
0 bipush 102 istore_13 iinc 1 by 16 return
如果只对变量进行++i或者i++操作,可以看到字节码指令是完全一样的,所以在性能上并没有什么不同,两者完全可以替换使用。两段代码的字节码指令的含义如下:
- (1)bipush 10表示把常量10放入操作数栈。
- (2)istore_1表示从操作数栈弹出10放入局部变量表,此时操作数栈为空,局部变量表索引为1的槽位存储10(描述符索引为i)。
- (3)iinc 1 by 1表示对局部变量表中索引为1的槽位中的数值进行加1操作,即更改为11,这便是上面代码的所有流程。
再看另外一段代码,当自增运算符和其他运算符混合运算时,代码清单如下所示:
方法method3( )对应的字节码指令如下:
对代码的解析如下:
- (1)执行bipush 10指令,此时操作数栈存放数据10。
- (2)执行istore_1指令,此时把数据10放入局部变量表1的位置,同时把栈中数据弹出,栈为空。
- (3)执行iload_1指令,此时把局部变量表中1号位置中的数据10放入操作数栈。
- (4)执行iinc 1 by 1指令,局部变量表中1号位置的数据加1,1号位置的数据变为11。
- (5)执行istore_2指令,此时把操作数栈中的10放入局部变量表2的位置,同时把栈中数据弹出,栈为空,此时局部变量表中1号位置存放的数据为11,2号位置存放的数据是10,也就是我们常说的先赋值再进行自增操作,所以此时a的值为10,i的值为11。
- (6)执行bipush 20指令,此时操作数栈存放数据20。
- (7)执行istore_3指令,此时把数据20放入局部变量表3的位置,同时把栈中数据弹出,栈为空。
- (8)执行iinc 3 by 1指令,局部变量表中3号位置的数据加1,3号位置的数据变为21。
- (9)执行iload_3指令,此时把局部变量表中3号位置中的数据21放入操作数栈。
- (10)执行istore_4指令,此时把操作数栈中的21放入局部变量表4的位置,同时把栈中数据弹出,栈为空,此时局部变量表中3号位置存放的数据为21,4号位置存放的数据是21,也就是我们常说的先自增再进行赋值操作,所以此时j的值为21,b的值为21。
3.2、比较指令
比较指令的作用是比较栈顶两个元素的大小,并将比较结果入栈。比较指令有dcmpg、dcmpl、fcmpg、fcmpl、lcmp。与前面讲解的指令类似,首字符d表示double类型,f表示float,l表示long。
可以发现,对于double和float类型的数字,分别有两套指令,即xcmpg和xcmpl(x取值d或f),以float为例,有fcmpg和fcmpl两个指令,它们的区别在于在数字比较时,若遇到NaN值,处理结果不同,指令dcmpl和dcmpg也是类似。指令lcmp针对long型整数,由于long型整数没有NaN值,故无须准备两套指令。
数值类型的数据才可以比较大小,例如byte、short、char、int、long、float、double类型的数据可以比较大小,但是boolean和引用数据类型的数据不能比较大小。
4、类型转换指令
类型转换指令可以将两种不同的数值类型进行相互转换。这些转换操作一般用于实现用户代码中的显式类型转换操作,或者用来处理数据类型相关指令与数据类型无法一一对应的问题。类型转换指令又分为宽化类型转换和窄化类型转换。
4.1、宽化类型转换
宽化类型转换简单来说就是把小范围类型向大范围类型转换,它是隐式转换,也可以理解为自动类型转换,不需要强制类型转换。
宽化类型转换是不会因为超过目标类型最大值而丢失信息的,例如,从int转换到long,或者从int转换到double,都不会丢失任何信息,转换前后的值是精确相等的。
但是从int、long类型数值转换到float,或者long类型数值转换到double时,将可能发生精度损失,可能丢失掉几个最低有效位上的值,转换后的浮点数值是根据IEEE 754最接近舍入模式所得到的正确整数值。尽管宽化类型转换实际上是可能发生精度损失的,但是这种转换永远不会导致JVM抛出运行时异常。
4.2、窄化类型转换
对应宽化类型转换的隐式转换,窄化类型转换就是显示类型转换或者强制类型转换。
窄化类型转换可能会导致转换结果具备不同的正负号、不同的数量级,因此,转换过程很可能会导致数值丢失精度。尽管数据类型窄化转换可能会发生上限溢出、下限溢出和精度损失等情况,但是JVM规范中明确规定数值类型的窄化转换指令永远不可能导致虚拟机抛出运行时异常。
5、对象、数组的创建与访问指令
Java作为一门面向对象语言,创建和访问对象是其一大特点,JVM也在字节码层面为其提供了一些指令专门用于操作类的对象。这类指令细分为创建指令、字段访问指令、数组操作指令和类型检查指令。
5.1、创建指令
虽然类实例和数组都是对象,但JVM对类实例和数组的创建与操作使用了不同的字节码指令。
1、创建类实例的指令:
创建类实例的指令是new,它接收一个操作数,操作数为指向常量池的索引,表示要创建的类型,执行完成后,将对象的引用压入操作数栈。
2、创建数组的指令:
创建数组的指令包含newarray、anewarray和multianewarray。newarray负责创建基本类型数组,anewarray负责创建引用类型数组,multianewarray负责创建多维数组。
5.2、字段访问指令
对象创建后,就可以通过对象访问指令获取对象实例或数组实例中的字段或者数组元素。访问类字段(static字段,或者称为类变量)的指令包括getstatic和putstatic。访问类实例字段(非static字段,或者称为实例变量)的指令包括getfield和putfield。
5.3、数组操作指令
数组操作指令主要包含xaload和xastore指令。xaload指令表示把一个数组元素加载到操作数栈的指令。根据不同的类型数组操作指令又分为baload、caload、saload、iaload、laload、faload、daload和aaload。指令前面第一个字符表示指令对应的数据类型,比如saload和caload分别表示操作short类型数组和char类型数组。指令xaload在执行时,要求操作数中栈顶元素为数组索引i,栈顶顺位第2个元素为数组引用a,该指令会弹岀栈顶这两个元素,并将a[i]重新压入栈。
xastore指令表示将一个操作数栈的值存储到数组元素中。根据不同类型数组操作指令又分为bastore、castore、sastore、iastore、lastore、fastore、dastore和aastore。指令前面第一个字符表示指令对应的数据类型,比如iastore指令表示给一个int数组的给定索引赋值。在iastore执行前,操作数栈顶需要准备3个元素,分别是赋值给数组的值、索引(数组角标)、数组引用,iastore会弹出这3个值,并将值赋给数组中指定索引的位置。
不同类型数组和数组操作指令的对应关系如下表所示:
5.4、类型检查指令
检查类实例或数组类型的指令主要包括instanceof和checkcast。指令instanceof用来判断给定对象是否为某一个类的实例,它会将判断结果压入操作数栈。指令checkcast用于检查类型强制转换是否可以进行。这两个指令很相似,区别在于checkcast指令如果可以强制类型转换,那么checkcast指令不会改变操作数栈,否则它会抛出“ClassCastException”异常,instanceof指令会将判断结果压入操作数栈,如果某对象属于某一个类的实例,将1压入操作数栈,否则将0压入操作数栈。
6、方法调用与返回指令
6.1、方法调用指令
方法调用指令包括invokevirtual、invokeinterface、invokespecial、invokestatic和invokedynamic,上述5条指令含义如下表所示:
6.2、方法返回指令
方法调用结束前,需要返回方法调用结果。方法返回指令是根据返回值的类型区分的,包括ireturn(当返回值是boolean、byte、char、short和int类型时使用)、lreturn、freturn、dreturn和areturn,另外还有一条return指令供声明返回值为void的方法、实例初始化方法以及类和接口的类初始化方法使用。方法返回指令如下表所示:
例如,ireturn指令表示将当前方法操作数栈中的栈顶元素弹出,并将这个元素压入调用者方法的操作数栈中,因为调用者非常关心方法的返回值,所有在当前方法操作数栈中的其他元素都会被丢弃。如果当前返回的是synchronized()方法,那么还会执行一个隐含的monitorexit指令,退出临界区。
7、操作数栈管理指令
JVM提供的操作数栈管理指令,可以直接作用于操作数栈,和数据结构中的栈操作类似,都会有入栈和出栈的操作,这类指令如下表所示:
pop指令表示将栈顶的1个32位的元素(比如int类型)出栈,即该元素占用一个slot即可。pop2指令表示将栈顶的1个64位的元素(比如long、double类型)或2个32位的元素出栈,即弹出操作数栈栈顶的2个slot。
dup和dup2表示复制栈顶数据并压入栈顶,dup后面的数字表示要复制的slot个数。dup开头的指令用于复制1个32位元素数据,即复制1个slot中的元素。dup2开头的指令用于复制1个64位或2个32位元素数据,即复制2个slot中的元素。带_x的指令是复制栈顶数据并插入栈顶以下的某个位置。4个指令分别是dup_x1、dup_x2、dup2_x1、dup2_x2。
8、控制转移指令
程序的执行流程一般都会包含条件跳转语句,相应的JVM提供了大量字节码指令用于实现程序的条件跳转,这些字节码指令我们称为控制转移指令,用于让程序有条件或者无条件地跳转到指定指令处。这些指令大体上可以分为比较指令、条件跳转指令、比较条件跳转指令、多条件分支跳转指令和无条件跳转指令等。
8.1、条件跳转指令
条件跳转指令通常和比较指令结合使用。在条件跳转指令执行前,一般可以先用比较指令进行栈顶元素的准备,然后进行条件跳转。条件跳转指令的格式为if_ ,以“if_”开头,的值包括eq(等于)、ne(不等于)、lt(小于)、le(小于或等于)、gt(大于)和ge(大于或等于),这些指令的意思都是弹出栈顶元素,然后和0比较,当满足给定的条件时则跳转到给定位置。此外还有两条指令用于判断是否为空,分别是ifnull和ifnonnull,条件跳转指令详细说明如下表所示:
与前面运算规则一致,对于boolean、byte、char、short类型的条件分支比较操作,都是使用int类型的比较指令完成。对于long、float、double类型的条件分支比较操作,则会先执行相应类型的比较运算指令,运算指令会返回一个整型值到操作数栈中,随后再执行int类型的条件分支比较操作来完成整个分支跳转。由于各类型的比较最终都会转为int类型的比较操作,所以JVM提供的int类型的条件分支指令是最为丰富和强大的。
8.2、比较条件跳转指令
比较条件跳转指令类似于比较指令和条件跳转指令的结合体,它将比较和跳转两个步骤合二为一。这类指令的格式可分为if_icmp和if_acmp,以“if_”开头,紧跟着第一个字母表示对应的数据类型,比如字符“i”开头的指令针对int型整数操作(也包括short和byte类型),以字符“a”开头的指令表示对象引用的比较。的值包括eq(等于)、ne(不等于),lt(小于)、le(小于或等于)、gt(大于)和ge(大于或等于)。
if_icmp类型的指令细化为具体指令包括if_icmpeq、if_icmpne、if_icmplt、if_icmple、if_icmpgt和if_icmpge。if_acmp类型的指令细化为具体指令包括if_acmpeq和if_acmpne。
这些指令都接收两个字节的操作数作为参数,用于计算跳转到新的指令地址执行。指令执行时,弹出栈顶两个元素进行比较,如果比较结果成立,则跳转到新的指令地址处继续执行;否则在该指令之后的指令地址处继续执行,注意比较结果没有任何数据入栈。关于各个指令的详细说明如下表所示:
8.3、多条件分支跳转
多条件分支跳转指令是专为Java语法中switch-case语句设计的,包含tableswitch和lookupswitch,如下表所示:
8.4、无条件跳转
JVM目前主要使用的无条件跳转指令包括goto和goto_w。指令goto接收两个字节的无符号操作数,共同构造一个带符号的整数,用于指定目标指令地址的偏移量。goto指令的作用就是跳转到偏移量给定的位置处,目标指令地址必须和go指令在同一个方法中。goto_w和goto作用相同,区别是goto_w接收4个字节的无符号操作数,可以构造范围更大的地址偏移量。指令jsr、jsr_w、ret虽然也是无条件跳转的,但主要用于try-finally语句,且已经被虚拟机逐渐废弃,不再过多赘述,无条件跳转指令如下表所示:
9、异常处理指令
9.1、抛出异常指令
在Java程序中显示抛出异常的操作(throw语句)都是由athrow指令来实现。除了使用throw语句显示抛出异常情况之外,JVM规范还规定了许多运行时异常会在其他JVM指令检测到异常状况时自动抛出,例如之前介绍整数运算时,当除数为零时,虚拟机会在idiv或ldiv指令中抛出ArithmeticException异常。正常情况下,操作数栈的压入弹出都是一条条指令完成的。唯一的例外情况是在抛出异常时,JVM会清除操作数栈上的所有内容,而后将异常实例压入调用者操作数栈上。
9.2、异常处理和异常表
在JVM中,处理异常(catch语句)不是由字节码指令来实现的,而是采用异常表来完成的。
如果一个方法定义了一个try-catch或者try-finally的异常处理,就会创建一个异常表。它包含了每个异常处理或者finally块的信息。异常表保存了每个异常处理信息,比如异常的起始位置、结束位置、程序计数器记录的代码处理的偏移地址、被捕获的异常类在常量池中的索引等信息。当一个异常被抛出时,JVM会在当前的方法里寻找一个匹配的处理,如果没有找到,这个方法会强制结束并弹出当前栈帧,并且异常会重新抛给上层调用的方法(在调用方法栈帧)。如果在所有栈帧弹出前仍然没有找到合适的异常处理,这个线程将终止。如果这个异常在最后一个非守护线程里抛出,将会导致JVM自己终止,比如这个线程是个main线程。不管什么时候抛出异常,如果异常处理最终匹配了所有异常类型,代码就会继续执行。如果方法结束后没有抛出异常,仍然执行finally块,在return前,它直接跳到finally块来完成目标。
10、同步控制指令
JVM支持两种同步结构,分别是方法内部一段指令序列的同步和方法级的同步,这两种同步都是使用monitor来支持的。
10.1、方法内指定指令序列的同步
ava中通常是由synchronized语句代码块来表示同步。JVM通过monitorenter和monitorexit两条指令支持synchronized关键字的语义。在JVM中,任何对象都有一个监视器与之相关联,用来判断对象是否被锁定,当监视器被持有后,对象处于锁定状态,指令monitorenter和monitorexit在执行时,都需要在操作数栈顶压入对象。monitorenter和monitorexit的锁定和释放都是针对这个对象的监视器进行的。
如果一段代码块使用了synchronized关键字修饰,当一个线程进入同步代码块时,在字节码指令层面会使用monitorenter指令表示有线程请求进入,如果锁定的当前对象的监视器的计数器为0,则该线程会被准许进入;若为1,则判断持有当前监视器的线程是否当前线程,如果是,则允许该线程继续进入(重入锁),否则进行等待,直到对象的监视器计数器为0,当前线程才会被允许进入同步块。当线程退岀同步代码块时,需要使用monitorexit指令声明退出。下图展示了监视器如何保护临界区代码不同时被多个线程访问,只有当线程4离开临界区后,线程1、2、3才有可能进入。
代码清单如下演示了同步控制指令的使用:
其对应的字节码指令如下:
可以看到monitorenter指令和monitorexit指令都是为同步代码块服务的。需要注意的是编译器必须确保无论方法通过何种方式完成,方法中调用过的每条monitorenter指令都必须执行其对应的monitorexit指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时monitorenter和monitorexit指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行monitorexit指令,所以会多出一个monitorexit指令。
10.2、方法级的同步
方法级的同步是隐式的,即无须通过字节码指令来控制,它实现在方法调用和返回操作之中。虚拟机可以从方法常量池的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法是否声明为同步方法。当调用方法时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否设置。如果设置了,执行线程将先持有同步锁,然后执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放同步锁。在方法执行期间,执行线程持有了同步锁,其他任何线程都无法再获得同一个锁。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的锁将在异常抛到同步方法之外时自动释放,如下代码清单所示:
这是一段很简单的代码,在add( )方法上加synchronized关键字修饰,对应的字节码指令如下,可以看到字节码指令中并没有使用monitorenter和monitorexit进行同步控制,但是方法设置了ACC_SYNCHRONIZED访问标志:
11、小结
讲解了字节码指令按不同的作用可以分为不同的种类,主要包括加载与存储指令、算数指令、类型转换指令、对象的创建与访问指令、方法调用与返回指令、操作数栈管理指令、控制转移指令、异常处理指令和同步控制指令。通过学习这些字节码指令,可以更好地理解Java中各种语法背后的原理和Java语言特性。
相关文章:

JVM字节码与类加载——字节码指令集与解析
文章目录 1、概述1.1、字节码与数据类型1.2、指令分类 2、加载与存储指令2.1、局部变量入栈指令2.2、常量入栈指令2.3、出栈装入局部变量表指令 3、算术指令3.1、彻底理解i与i3.2、比较指令 4、类型转换指令4.1、宽化类型转换4.2、窄化类型转换 5、对象、数组的创建与访问指令5…...

景芯2.5GHz A72训练营dummy添加(一)
景芯A72做完布局布线之后导出GDS,然后进行GDS merge,然后用Calibre对Layout添加Dummy。在28nm以及之前的工艺中,Dummy metal对Timing的影响不是很大,当然Star RC也提供了相应的解决方案,可以考虑Dummy metal来抽取RC。…...

React - 请你说一说setState是同步的还是异步的
难度级别:中高级及以上 提问概率:70% 在React项目中,使用setState可以更新状态数据,而不能直接使用为this.state赋值的方式。而为了避免重复更新state数据,React首先将state添加到状态队列中,此时我们可以通过shouldComponentUpdate这个钩…...

设计模式之命令模式(下)
2)完整解决方案 1.结构图 FBSettingWindow是“功能键设置”界面类,FunctionButton充当请求调用者,Command充当抽象命令类,MinimizeCommand和HelpCommand充当具体命令类,WindowHanlder和HelpHandler充当请求接收者。 …...

【opencv】示例-demhist.cpp 调整图像的亮度和对比度,并在GUI窗口中实时显示调整后的图像以及其直方图。...
#include "opencv2/core/utility.hpp" // 包含OpenCV核心工具库的头文件 #include "opencv2/imgproc.hpp" // 包含OpenCV图像处理的头文件 #include "opencv2/imgcodecs.hpp" // 包含OpenCV图像编码解码的头文件 #include "opencv2/highgui…...
计算机网络---第三天
OSI参考模型与TCP/IP模型 参考模型产生背景: 背景:①兼容性较差,接口不统一 ②不利于排错与维护 ③设备成本高 参考模型概念: 概念:OSI参考模型定义了网络中设备所遵守的层次结构 参考模型优点: 优点…...

怎么防止文件被拷贝,复制别人拷贝电脑文件
怎么防止文件被拷贝,复制别人拷贝电,脑文件 防止文件被拷贝通常是为了保护敏感数据、知识产权或商业秘密不被未经授权的人员获取或传播。以下列出了一系列技术手段和策略,可以帮助您有效地防止文件被拷贝。 1. 终端管理软件: 如安企神、域智…...

流式密集视频字幕
流式密集视频字幕 摘要1 IntroductionRelated Work3 Streaming Dense Video Captioning Streaming Dense Video Captioning 摘要 对于一个密集视频字幕生成模型,预测在视频中时间上定位的字幕,理想情况下应该能够处理长的输入视频,预测丰富、…...

【教程】iOS Swift应用加固
🔒 保护您的iOS应用免受恶意攻击!在本篇博客中,我们将介绍如何使用HTTPCORE DES加密来加固您的应用程序,并优化其安全性。通过以下步骤,您可以确保您的应用在运行过程中不会遭受数据泄露和未授权访问的风险。 摘要 …...
新型基础设施建设(新基建)
新型基础设施建设(新基建)主要包括七个方面,即5G基站建设、特高压、城际高速铁路和城市轨道交通、新能源汽车充电桩、大数据中心、人工智能和工业互联网。 以下是新型基础设施的详细内容: 一、5G基站建设。5G网络的扩展和优化&a…...
蓝桥杯 第 9 场 小白入门赛 字符迁移
题目: 3.字符迁移【算法赛】 - 蓝桥云课 (lanqiao.cn) 思路: 此题通过把小写字母映射成数字,进行差分即可。 AC代码: #include<iostream> #include<cstring> #include<algorithm>using namespace std;typed…...

泰迪智能科技人工智能应用工程师(中级)特训营
随着人工智能技术的迅猛发展和应用的不断拓展,掌握人工智能技术已成为现代职业发展和企业创新的关键。为此,人工智能技能提升特训营应运而生,以全面、系统的课程设置,帮助学员深入掌握相关的理论知识,实践操作技能。特…...

【数据结构】考研真题攻克与重点知识点剖析 - 第 6 篇:图
前言 本文基础知识部分来自于b站:分享笔记的好人儿的思维导图与王道考研课程,感谢大佬的开源精神,习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析,本人技术…...
java的基本数据类型
在Java编程语言中,基本数据类型是构成Java程序的基础元素,它们用于存储简单值。Java的基本数据类型可以分为两大类:原始类型(Primitive Types)和引用类型(Reference Types)。原始类型包括整型、…...

0104练习与思考题-算法基础-算法导论第三版
2.3-1 归并示意图 问题:使用图2-4作为模型,说明归并排序再数组 A ( 3 , 41 , 52 , 26 , 38 , 57 , 9 , 49 ) A(3,41,52,26,38,57,9,49) A(3,41,52,26,38,57,9,49)上的操作。图示: tips::有不少在线算法可视化工具(软…...

烤羊肉串引来的思考--命令模式
1.1 吃羊肉串! 烧烤摊旁边等着拿肉串的人七嘴八舌地叫开了。场面有些混乱,由于人实在太多,烤羊肉串的老板已经分不清谁是谁,造成分发错误,收钱错误,烤肉质量不过关等。 外面打游击烤羊肉串和这种开门店做烤…...
Python 描述符
文章目录 类型:数据描述符:方法描述符:描述符的要包括以下几点:方法描述符实现缓存 描述符(Descriptor)是 Python 中一个非常强大的特性,它允许我们自定义属性的访问行为。使用描述符,我们可以创建一些特殊的属性,在访问这些属性时执行自定义…...
Go语言创建HTTP服务器
Web服务器可提供网页、Web服务和文件,而Go语言为创建Web服务器提供了强大的支持。 1.通过Hello World Web 服务器宣告您的存在 标准库中的net/http包提供了多种创建HTTP服务器的方法,它还提供了一个基本的路由器。 package mainimport ("net/http" )func helloWo…...
【LeetCode热题100】【栈】柱状图中最大的矩形
题目链接:84. 柱状图中最大的矩形 - 力扣(LeetCode) 要找最大的矩形就是要找以每根柱子为高度往两边延申的边界,要作为柱子的边界就必须高度不能低于该柱子,否则矩形无法同高,也就是需要找出以每根柱子为高…...

谷歌浏览器插件开发速成指南:弹窗
诸神缄默不语-个人CSDN博文目录 本文介绍谷歌浏览器插件开发的入门教程,阅读完本文后应该就能开发一个简单的“hello world”插件,效果是出现写有“Hello Extensions”的弹窗。 作为系列文章的第一篇,本文还希望读者阅读后能够简要了解在此基…...

如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...

Linux中《基础IO》详细介绍
目录 理解"文件"狭义理解广义理解文件操作的归类认知系统角度文件类别 回顾C文件接口打开文件写文件读文件稍作修改,实现简单cat命令 输出信息到显示器,你有哪些方法stdin & stdout & stderr打开文件的方式 系统⽂件I/O⼀种传递标志位…...
土建施工员考试:建筑施工技术重点知识有哪些?
《管理实务》是土建施工员考试中侧重实操应用与管理能力的科目,核心考查施工组织、质量安全、进度成本等现场管理要点。以下是结合考试大纲与高频考点整理的重点内容,附学习方向和应试技巧: 一、施工组织与进度管理 核心目标: 规…...
AWS vs 阿里云:功能、服务与性能对比指南
在云计算领域,Amazon Web Services (AWS) 和阿里云 (Alibaba Cloud) 是全球领先的提供商,各自在功能范围、服务生态系统、性能表现和适用场景上具有独特优势。基于提供的引用[1]-[5],我将从功能、服务和性能三个方面进行结构化对比分析&#…...
PCA笔记
✅ 问题本质:为什么让矩阵 TT 的行列式为 1? 这个问题通常出现在我们对数据做**线性变换(旋转/缩放)**的时候,比如在 PCA 中把数据从原始坐标系变换到主成分方向时。 📌 回顾一下背景 在 PCA 中ÿ…...