跳转到内容

2. 前端编译阶段及字节码文件

1726240236741

https://www.cnblogs.com/chanshuyi/p/jvm_serial_05_jvm_bytecode_analysis.html

1. 作用

字节码文件本质上是一个以 8 位字节为基础单位的二进制流文件,各个数据项目严格按照顺序紧凑的排列在 class 文件中。jvm 根据其特定的规则解析该二进制数据,从而得到相关信息。

从更高层次上抽象的话,如果 jvm 就是一个计算机操作系统,而字节码文件就是操作系统的指令集。高级语言通过各种各样的编译器生成字节码文件,然后通过字节码文件操纵 jvm 完成实际的程序功能

字节码增强技术 和 运行时类重载技术:

因为已经了解到字节码指令了,因此,我们就可以对字节码文件进行编辑,让字节码文件操作 jvm 完成某项功能。这就是所谓的字节码增强技术。

由于字节码增强技术只能用于操作字节码文件,对于已经加载到 jvm 中的 class 类对象,并没有办法进行操作,因此 jvm 对外提供了一整套的 api,可以用来专门解决这个问题。这一套 API 就是运行时类重载技术。

为什么要了解字节码?

  1. 有利于编写更加高效,性能更高的程序;
  2. 通过修改字节码来调整程序行为;
  3. 对于了解一些利用字节码相关技术完成软件功能的实现原理有帮助,如了解 AOP 的实现原理、Profiler 的实现原理、Mock 框架的实现原理等;
  4. 热部署、性能诊断工具等;

2. 查看 class 文件的方法

  1. javap 进行反编译;
  2. idea 中的 show bytecode;
  3. idea 插件中的 jclasslib 插件包;
  4. arths

3. Appendix:指令集

字节码指令根据功能、属性不同,可以分为 11 大类。下面附上字节码指令的分类,用于简单、临时查看,字节码指令的详细介绍,还需要查看官网的介绍。

Constants 常量相关
十进制操作码助记符含义
000x00nop什么都不做
010x01aconst_null把 null 推到操作数栈
020x02iconst_m1把 int 常量 –1 推到操作数栈
030x03iconst_0把 int 常量 0 推到操作数栈
040x04iconst_1把 int 常量 1 推到操作数栈
050x05iconst_2把 int 常量 2 推到操作数栈
060x06iconst_3把 int 常量 3 推到操作数栈
070x07iconst_4把 int 常量 4 推到操作数栈
080x08iconst_5把 int 常量 5 推到操作数栈
090x09lconst_0把 long 常量 0 推到操作数栈
100x0Alconst_1把 long 常量 1 推到操作数栈
110x0Bfconst_0把 float 常量 0 推到操作数栈
120x0Cfconst_1把 float 常量 1 推到操作数栈
130x0Dfconst_2把 float 常量 2 推到操作数栈
140x0Edconst_0把 double 常量 0 推到操作数栈
150x0Fdconst_1把 double 常量 1 推到操作数栈
160x10bipush把单字节常量(-128~127)推到操作数栈
170x11sipush把 short 常量(-32768~32767)推到操作数栈
180x12ldc把常量池中的 int,float,String 型常量取出并推到操作数栈顶
190x13ldc_w把常量池中的 int,float,String 型常量取出并推到操作数栈顶(宽索引)
200x14ldc2_w把常量池中的 long,double 型常量取出并推到操作数栈顶(宽索引)
Loads 加载相关
十进制操作码助记符含义
210x15iload把 int 型局部变量推到操作数栈
220x16lload把 long 型局部变量推到操作数栈
230x17fload把 float 型局部变量推到操作数栈
240x18dload把 double 型局部变量推到操作数栈
250x19aload把引用型局部变量推到操作数栈
260x1Aiload_0把局部变量第 1 个 int 型局部变量推到操作数栈
270x1Biload_1把局部变量第 2 个 int 型局部变量推到操作数栈
280x1Ciload_2把局部变量第 3 个 int 型局部变量推到操作数栈
290x1Diload_3把局部变量第 4 个 int 型局部变量推到操作数栈
300x1Elload_0把局部变量第 1 个 long 型局部变量推到操作数栈
310x1Flload_1把局部变量第 2 个 long 型局部变量推到操作数栈
320x20lload_2把局部变量第 3 个 long 型局部变量推到操作数栈
330x21lload_3把局部变量第 4 个 long 型局部变量推到操作数栈
340x22fload_0把局部变量第 1 个 float 型局部变量推到操作数栈
350x23fload_1把局部变量第 2 个 float 型局部变量推到操作数栈
360x24fload_2把局部变量第 3 个 float 型局部变量推到操作数栈
370x25fload_3把局部变量第 4 个 float 型局部变量推到操作数栈
380x26dload_0把局部变量第 1 个 double 型局部变量推到操作数栈
390x27dload_1把局部变量第 2 个 double 型局部变量推到操作数栈
400x28dload_2把局部变量第 3 个 double 型局部变量推到操作数栈
410x29dload_3把局部变量第 4 个 double 型局部变量推到操作数栈
420x2Aaload_0把局部变量第 1 个引用型局部变量推到操作数栈
430x2Baload_1把局部变量第 2 个引用型局部变量推到操作数栈
440x2Caload_2把局部变量第 3 个引用型局部变量推到操作数栈
450x2Daload_3把局部变量第 4 个引用 型局部变量推到操作数栈
460x2Eiaload把 int 型数组指定索引的值推到操作数栈
470x2Flaload把 long 型数组指定索引的值推到操作数栈
480x30faload把 float 型数组指定索引的值推到操作数栈
490x31daload把 double 型数组指定索引的值推到操作数栈
500x32aaload把引用型数组指定索引的值推到操作数栈
510x33baload把 boolean 或 byte 型数组指定索引的值推到操作数栈
520x34caload把 char 型数组指定索引的值推到操作数栈
530x35saload把 short 型数组指定索引的值推到操作数栈
Loads Store 存储相关
十进制操作码助记符含义
540x36istore把栈顶 int 型数值存入指定局部变量
550x37lstore把栈顶 long 型数值存入指定局部变量
560x38fstore把栈顶 float 型数值存入指定局部变量
570x39dstore把栈顶 double 型数值存入指定局部变量
580x3Aastore把栈顶引用型数值存入指定局部变量
590x3Bistore_0把栈顶 int 型数值存入第 1 个局部变量
600x3Cistore_1把栈顶 int 型数值存入第 2 个局部变量
610x3Distore_2把栈顶 int 型数值存入第 3 个局部变量
620x3Eistore_3把栈顶 int 型数值存入第 4 个局部变量
630x3Flstore_0把栈顶 long 型数值存入第 1 个局部变量
640x40lstore_1把栈顶 long 型数值存入第 2 个局部变量
650x41lstore_2把栈顶 long 型数值存入第 3 个局部变量
660x42lstore_3把栈顶 long 型数值存入第 4 个局部变量
670x43fstore_0把栈顶 float 型数值存入第 1 个局部变量
680x44fstore_1把栈顶 float 型数值存入第 2 个局部变量
690x45fstore_2把栈顶 float 型数值存入第 3 个局部变量
700x46fstore_3把栈顶 float 型数值存入第 4 个局部变量
710x47dstore_0把栈顶 double 型数值存入第 1 个局部变量
720x48dstore_1把栈顶 double 型数值存入第 2 个局部变量
730x49dstore_2把栈顶 double 型数值存入第 3 个局部变量
740x4Adstore_3把栈顶 double 型数值存入第 4 个局部变量
750x4Bastore_0把栈顶 引用 型数值存入第 1 个局部变量
760x4Castore_1把栈顶 引用 型数值存入第 2 个局部变量
770x4Dastore_2把栈顶 引用 型数值存入第 3 个局部变量
780x4Eastore_3把栈顶 引用 型数值存入第 4 个局部变量
790x4Fiastore把栈顶 int 型数值存入数组指定索引位置
800x50lastore把栈顶 long 型数值存入数组指定索引位置
810x51fastore把栈顶 float 型数值存入数组指定索引位置
820x52dastore把栈顶 double 型数值存入数组指定索引位置
830x53aastore把栈顶 引用 型数值存入数组指定索引位置
840x54bastore把栈顶 boolean or byte 型数值存入数组指定索引位置
850x55castore把栈顶 char 型数值存入数组指定索引位置
860x56sastore把栈顶 short 型数值存入数组指定索引位置
Loads Stack 栈相关
十进制操作码助记符含义
870x57pop把栈顶数值弹出(非 long,double 数值)
880x58pop2把栈顶的一个 long 或 double 值弹出,或弹出 2 个其他类型数值
890x59dup复制栈顶数值并把数值入栈
900x5Adup_x1复制栈顶数值并将两个复制值压入栈顶
910x5Bdup_x2复制栈顶数值并将三个(或两个)复制值压入栈顶
920x5Cdup2复制栈顶一个(long 或 double 类型的)或两个(其它)数值并将复制值压入栈顶
930x5Ddup2_x1dup_x1 指令的双倍版本
940x5Edup2_x2dup_x2 指令的双倍版本
950x5Fswap把栈顶端的两个数的值交换(数值不能是 long 或 double 类型< td >的)
Loads Math 运算相关

Java 虚拟机在处理浮点数运算时,不会抛出任何运行时异常,当一个操作产生溢出时,将会使用有符号的无穷大来表示,如果某个操作结果没有明确的数学定义的话,将会使用 NaN 值来表示。所有使用 NaN 值作为操作数的算术操作,结果都会返回 NaN。

十进制操作码助记符含义
960x60iadd把栈顶两个 int 型数值相加并将结果入栈
970x61ladd把栈顶两个 long 型数值相加并将结果入栈
980x62fadd把栈顶两个 float 型数值相加并将结果入栈
990x63dadd把栈顶两个 double 型数值相加并将结果入栈
1000x64isub把栈顶两个 int 型数值相减并将结果入栈
1010x65lsub把栈顶两个 long 型数值相减并将结果入栈
1020x66fsub把栈顶两个 float 型数值相减并将结果入栈
1030x67dsub把栈顶两个 double 型数值相减并将结果入栈
1040x68imul把栈顶两个 int 型数值相乘并将结果入栈
1050x69lmul把栈顶两个 long 型数值相乘并将结果入栈
1060x6Afmul把栈顶两个 float 型数值相乘并将结果入栈
1070x6Bdmul把栈顶两个 double 型数值相乘并将结果入栈
1080x6Cidiv把栈顶两个 int 型数值相除并将结果入栈
1090x6Dldiv把栈顶两个 long 型数值相除并将结果入栈
1100x6Efdiv把栈顶两个 float 型数值相除并将结果入栈
1110x6Fddiv把栈顶两个 double 型数值相除并将结果入栈
1120x70irem把栈顶两个 int 型数值模运算并将结果入栈
1130x71lrem把栈顶两个 long 型数值模运算并将结果入栈
1140x72frem把栈顶两个 float 型数值模运算并将结果入栈
1150x73drem把栈顶两个 double 型数值模运算并将结果入栈
1160x74ineg把栈顶 int 型数值取负并将结果入栈
1170x75lneg把栈顶 long 型数值取负并将结果入栈
1180x76fneg把栈顶 float 型数值取负并将结果入栈
1190x77dneg把栈顶 double 型数值取负并将结果入栈
1200x78ishl把 int 型数左移指定位数并将结果入栈
1210x79lshl把 long 型数左移指定位数并将结果入栈
1220x7Aishr把 int 型数右移指定位数并将结果入栈(有符号)
1230x7Blshr把 long 型数右移指定位数并将结果入栈(有符号)
1240x7Ciushr把 int 型数右移指定位数并将结果入栈(无符号)
1250x7Dlushr把 long 型数右移指定位数并将结果入栈(无符号)
1260x7Eiand把栈顶两个 int 型数值 按位与 并将结果入栈
1270x7Fland把栈顶两个 long 型数值 按位与 并将结果入栈
1280x80ior把栈顶两个 int 型数值 按位或 并将结果入栈
1290x81lor把栈顶两个 long 型数值 按或与 并将结果入栈
1300x82ixor把栈顶两个 int 型数值 按位异或 并将结果入栈
1310x83lxor把栈顶两个 long 型数值 按位异或 并将结果入栈
1320x84iinc把指定 int 型增加指定值
Loads Conversions 转换相关

类型转换指令可以将两种不同的数值类型进行相互转换,这些转换操作一般用于实现用户代码中的显示类型转换操作。 Java 虚拟机直接支持(即转换时无需显示的转换指令)小范围类型向大范围类型的安全转换,但在处理窄化类型转换时,必须显式使用转换指令来完成。

十进制操作码助记符含义
1330x85i2l把栈顶 int 强转 long 并入栈
1340x86i2f把栈顶 int 强转 float 并入栈
1350x87i2d把栈顶 int 强转 double 并入栈
1360x88l2i把栈顶 long 强转 int 并入栈
1370x89l2f把栈顶 long 强转 float 并入栈
1380x8Al2d把栈顶 long 强转 double 并入栈
1390x8Bf2i把栈顶 float 强转 int 并入栈
1400x8Cf2l把栈顶 float 强转 long 并入栈
1410x8Df2d把栈顶 float 强转 double 并入栈
1420x8Ed2i把栈顶 double 强转 int 并入栈
1430x8Fd2l把栈顶 double 强转 long 并入栈
1440x90d2f把栈顶 double 强转 float 并入栈
1450x91i2b把栈顶 int 强转 byte 并入栈
1460x92i2c把栈顶 int 强转 char 并入栈
1470x93i2s把栈顶 int 强转 short 并入栈
Loads Comparisons 比较相关
十进制操作码助记符含义
1480x94lcmp比较栈顶两 long 型数值大小,并将结果(1,0,-1)压入栈顶
1490x95fcmpl比较栈顶两 float 型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为“NaN”时,将-1 压入栈顶
1500x96fcmpg比较栈顶两 float 型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为“NaN”时,将 1 压入栈顶
1510x97dcmpl比较栈顶两 double 型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为“NaN”时,将-1 压入栈顶
1520x98dcmpg比较栈顶两 double 型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为“NaN”时,将 1 压入栈顶
1530x99ifeq当栈顶 int 型数值等于 0 时,跳转
1540x9Aifne当栈顶 int 型数值不等于 0 时,跳转
1550x9Biflt当栈顶 int 型数值小于 0 时,跳转
1560x9Cifge当栈顶 int 型数值大于等于 0 时,跳转
1570x9Difgt当栈顶 int 型数值大于 0 时,跳转
1580x9Eifle当栈顶 int 型数值小于等于 0 时,跳转
1590x9Fif_icmpeq比较栈顶两个 int 型数值,等于 0 时,跳转
1600xA0if_icmpne比较栈顶两个 int 型数值,不等于 0 时,跳转
1610xA1if_icmplt比较栈顶两个 int 型数值,小于 0 时,跳转
1620xA2if_icmpge比较栈顶两个 int 型数值,大于等于 0 时,跳转
1630xA3if_icmpgt比较栈顶两个 int 型数值,大于 0 时,跳转
1640xA4if_icmple比较栈顶两个 int 型数值,小于等于 0 时,跳转
1650xA5if_acmpeq比较栈顶两个 引用 型数值,相等时跳转
1660xA6if_acmpne比较栈顶两个 引用 型数值,不相等时跳转
Loads Control 控制相关

控制转移指令可以让 Java 虚拟机有条件或无条件地从指定的位置指令而不是控制转移指令的下一条指令继续执行程序,从概念模型上理解,可以认为控制转移指令就是在有条件或无条件地修改 PC 寄存器的值。

十进制操作码助记符含义
1670xA7goto无条件分支跳转
1680xA8jsr跳转至指定 16 位 offset(bit) 位置,并将 jsr 下一条指令地址压入栈顶
1690xA9ret返回至局部变量指定的 index 的指令位置(一般与 jsr,jsr_w 联合使用)
1700xAAtableswitch用于 switch 条件跳转,case 值连续(可变长度指令)
1710xABlookupswitch用于 switch 条件跳转,case 值不连续(可变长度指令)
1720xACireturn结束方法,并返回一个 int 类型数据
1730xADlreturn从当前方法返回 long
1740xAEfreturn从当前方法返回 float
1750xAFdreturn从当前方法返回 double
1760xB0areturn从当前方法返回 对象引用
1770xB1return从当前方法返回 void
Loads references 引用、方法、异常、同步相关
十进制操作码助记符含义
1780xB2getstatic获取指定类的静态域,并将其值压入栈顶
1790xB3putstatic为类的静态域赋值
1800xB4getfield获取指定类的实例域(对象的字段值),并将其值压入栈顶
1810xB5putfield为指定的类的实例域赋值
1820xB6invokevirtual调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),是 Java 语言中最常见的方法分派方式。
1830xB7invokespecial调用一些需要特殊处理的实例方法,包括实例初始化方法()、私有方法和父类方法。这三类方法的调用对象在编译时就可以确定。
1840xB8invokestatic调用静态方法
1850xB9invokeinterface调用接口方法调,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。
1860xBAinvokedynamic调用动态链接方法(该指令是指令是 Java SE 7 中新加入的)。用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法,前面 4 条调用指令的分派逻辑都固化在 Java 虚拟机内部,而 invokedynamic 指令的分派逻辑是由用户所设定的引导方法决定的。
1870xBBnew创建一个对象,并将其引用值压入栈顶
1880xBCnewarray创建一个指定原始类型(如 int、float、char……)的数组,并将其引用值压入栈顶
1890xBDanewarray创建一个引用型(如类,接口,数组)的数组,并将其引用值压入栈顶
1900xBEarraylength获得数组的长度值并压入栈顶
1910xBFathrow将栈顶的异常直接抛出。Java 程序中显式抛出异常的操作(throw 语句)都由 athrow 指令来实现,并且,在 Java 虚拟机中,处理异常(catch 语句)不是由字节码指令来实现的,而是采用异常表来完成的。
1920xC0checkcast检验类型转换,检验未通过将抛出 ClassCastException
1930xC1instanceof检验对象是否是指定的类的实例,如果是将 1 压入栈顶,否则将 0 压入栈顶
1940xC2monitorenter获取对象的 monitor,用于同步块或同步方法
1950xC3monitorexit释放对象的 monitor,用于同步块或同步方法

Java 虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用管程(Monitor)来支持的。 **方法级的同步是隐式的,即无须通过字节码指令来控制,它实现在方法调用和返回操作之中。**虚拟机可以从方法常量池的方法表结构中的 ACC_SYNCHRONIZED 方法标志得知一个方法是否声明为同步方法。当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程就要求先成功持有管程,然后才能执行方法,最后当方法完成(无论是正常完成还是非正常完成)时释放管程。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那么这个同步方法所持有的管程将在异常抛到同步方法之外时自动释放。 同步一段指令集序列通常是由 Java 语言中的 synchronized 语句块来表示的,Java 虚拟机的指令集中有 monitorenter 和 monitorexit 两条指令来支持 synchronized 关键字的语义 编译器必须确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都必须执行其对应的 monitorexit 指令,而无论这个方法是正常结束还是异常结束。

Loads Extended 扩展相关
十进制操作码助记符含义
1960xC4wide扩展访问局部变量表的索引宽度
1970xC5multianewarray创建指定类型和指定维度的多维数组(执行该指令时,操作栈中必须包含各维度的长度值),并将其引用值压入栈顶
1980xC6ifnull为 null 时跳转
1990xC7ifnonnull非 null 时跳转
2000xC8goto_w无条件跳转(宽索引)
2010xC9jsr_w跳转指定 32bit 偏移位置,并将 jsr_w 下一条指令地址入栈
Loads Reserved 保留指令
十进制操作码助记符含义
2020xCAbreakpoint调试时的断点
2540xFEimpdep1用于在特定硬件中使用的语言后门
2550xFFimpdep2用于在特定硬件中使用的语言后门

作者:刘 Java 链接:https://juejin.cn/post/7027707475503611940 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

4. 参考

make it come true