JVM3️⃣字节码技术①类文件结构、指令执行流程

1、类文件结构

类的字节码文件,是以 16 进制数表示的。

根据 JVM 规范,类文件结构包含以下信息:

  • 魔数:文件类型,class 类型的魔数为 cafe babe

  • 版本:类的版本,0x0034(十进制 52)表示 Java 8

  • 常量池

  • 访问标识、继承、接口信息

  • 成员变量

  • 方法

  • 附加属性

    ClassFile {
        u4 magic;
        u2 minor_version;
        u2 major_version;
        u2 constant_pool_count;
        cp_info constant_pool[constant_pool_count-1];
        u2 access_flags;
        u2 this_class;
        u2 super_class;
        u2 interfaces_count;
        u2 interfaces[interfaces_count];
        u2 fields_count;
        field_info fields[fields_count];
        u2 methods_count;
        method_info methods[methods_count];
        u2 attributes_count;
        attribute_info attributes[attributes_count];
    }
    

javap

  1. 16 进制的 class 文件可读性很差,需要反编译。
  2. Oracle 提供 javap工具,用于反编译 class 文件。
  3. 指令javap -v HelloWorld.class

2、指令执行流程(❗)

2.1、图示流程

使用图形 + 字节码指令,演示字节码指令的详细执行过程。

public class Demo3_1 {
	public static void main(String[] args) {
		int a = 10;
		int b = Short.MAX_VALUE + 1;
		int c = a + b;
		System.out.println(c);
	}
}

2.1.1、字节码文件

展示常量池、方法信息。

  • stack操作数栈的容量。

  • locals局部变量表的长度,每个位置称为 slot。

  • args_size方法参数个数。

    Constant pool:
    	#1 = Methodref		#7.#26		// java/lang/Object."<init>":()V
    	#2 = Class			#27			// java/lang/Short
    	#3 = Integer		32768
    	#4 = Fieldref		#28.#29		// java/lang/System.out:Ljava/io/PrintStream;
    	#5 = Methodref		#30.#31		// java/io/PrintStream.println:(I)V
    	#6 = Class 			#32			// cn/itcast/jvm/t3/bytecode/Demo3_1
    	#7 = Class 			#33			// java/lang/Object
    	#8 = Utf8 			<init>
    	#9 = Utf8 			()V
    	#10 = Utf8 			Code
    	#11 = Utf8 			LineNumberTable
    	#12 = Utf8 			LocalVariableTable
    	#13 = Utf8 			this
    	#14 = Utf8 			Lcn/itcast/jvm/t3/bytecode/Demo3_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 			MethodParameters
    	#24 = Utf8 			SourceFile
        #25 = Utf8 			Demo3_1.java
        #26 = NameAndType 	#8:#9		// "<init>":()V
        #27 = Utf8 			java/lang/Short
        #28 = Class 		#34			// java/lang/System
        #29 = NameAndType	#35:#36		// out:Ljava/io/PrintStream;
        #30 = Class 		#37			// java/io/PrintStream
        #31 = NameAndType	#38:#39		// println:(I)V
        #32 = Utf8			cn/itcast/jvm/t3/bytecode/Demo3_1
        #33 = Utf8			java/lang/Object
        #34 = Utf8			java/lang/System
        #35 = Utf8			out
        #36 = Utf8			Ljava/io/PrintStream;
        #37 = Utf8			java/io/PrintStream
        #38 = Utf8			printl
        #39 = Utf8			(I)V
    {
        public cn.itcast.jvm.t3.bytecode.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 6: 0
                LocalVariableTable:
                	Start Length Slot Name Signature
                      0 	5	  0   this Lcn/itcast/jvm/t3/bytecode/Demo3_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 8: 0
                    line 9: 3
                    line 10: 6
                    line 11: 10
                    line 12: 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
                MethodParameters:
                	Name		Flags
                	args
    }
    

2.1.2、类加载

常量池载入运行时常量池,方法字节码载入方法区

image-20220228221902335

2.1.3、main 线程

虚拟机栈与线程同时创建,线程私有

  • JVM 为每个线程分配一个虚拟机栈。
  • 虚拟机栈 为每个方法分配一个栈帧
  • 每个栈帧都会分配局部变量表和操作数栈。
    • 局部变量表
      1. 超过 short 范围的整数才会保存到常量池
      2. 否则会保存在字节码指令中。
    • 操作数栈
      1. 每个栈帧为 4 个字节
      2. 入栈时不足用 0 补齐,超过时分 2 次入栈。

主线程开始运行,分配栈帧内存。

  • 操作数栈:stack = 2,容量为 2。

  • 局部变量表:locals = 4,长度为 4。

    image-20220228222219406

2.1.4、执行字节码

  1. bipush 10:将整数 10 入栈。

    image-20220228231417962
  2. istore_1:将栈顶数据(int,10),保存到局部变量表中下标为 1 的 slot。

    image-20220228231437956
  3. ldc #3:将常量池 #3 的整数入栈(该整数值在编译期计算得出)。

    image-20220228234750440
  4. istore_2:将栈顶数据(int,32768),保存到局部变量表中下标为 2 的 slot。

    image-20220228235025919
  5. iload_1:将局部变量表中 slot 1 的数据(int,10),加载到操作数栈的栈顶。

    image-20220228235455967
  6. iload_2:将局部变量表中 slot 2 的数据(int,32768),加载到操作数栈的栈顶。

    image-20220228235605067
  7. iadd:将操作数栈的两个整数相加并出栈,求和结果入栈

    image-20220228235830710
  8. istore_3:将栈顶数据(int,32778),保存到局部变量表中下标为 3 的 slot

    image-20220301000024487
  9. getstatic #4:将常量池 #4 的静态变量入栈(对象在堆中的引用)

    image-20220301000459560
  10. iload_3:将局部变量表中 slot 3 的数据(int,32778),加载到操作数栈的栈顶。

    image-20220301000702452
  11. invokevirtual #5

    1. 加载常量池 #5(虚方法调用)

    2. 定位到对应方法java/io/PrintStream.println:(I)V

    3. 生成新的栈帧,分配 locals 和 stack 等。

    4. 将参数传入新的栈帧,执行新栈帧中的字节码指令。

      image-20220301010341946
    5. 新栈帧中的方法执行完毕后,将栈帧从虚拟机栈中出栈。

    6. 清除 main 栈帧的操作数栈内容。

      image-20220301010655153
  12. return:main() 方法执行完毕,将 main 栈帧从虚拟机栈中出栈。

  13. 程序结束。

2.2、字节码指令(❗)

2.2.1、局部变量表

保存

将操作数栈的栈顶数据保存到局部变量表中相应 slot。

  • astore:引用类型数据。
  • 其它 load:基本类型数据。
    • istore:int
    • lstore:long
    • fstore:float
    • dstore:double

加载

将局部变量表中相应位置的数据,加载到操作数栈的栈顶

  • aload:引用类型数据。
  • 其它 load:基本类型数据。

2.2.2、常量池

除了从局部变量表中加载数据,指令执行时加载的都是常量池的数据。

  • 静态变量:getstatic

  • 整数

    • iconst:取 -1 到 5 时
    • bipush:取 -128 到 127 时(byte 的范围)
    • sipush:取 -32768 到 32767 时(short 的范围)
    • ldc:取 -2147483648 到 2147483647 时(int 的范围)
    • ldc2_w:取 -9223372036854775808到 9223372036854775807时(long 的范围)

2.2.3、算数指令

  • 首字母 i、l、f、d,分别表示 int 、long、float、double
  • byte、short、char 和 boolean 类型,都以 int 类型的指令代替
int long float double
iadd ladd fadd dadd
isub lsub fsub dsub
imul lmul fmul dmul
idiv ldiv fdiv ddiv
求余 irem lrem frem drem
取反 ineg lneg fneg dneg
位移 ishl、ishr、iushr lshl、lshr、lushr
按位或 ior lor
按位与 iand land
按位异或 ixor lxor
局部变量自增 iinc
比较指令 lcmp fcmpg、fcmpl dcmp、dcmpl

2.2.4、条件判断

比较栈顶数据:与 0 比较

指令 助记符 含义
0x99 ifeq 判断是否 == 0
0x9a ifne 判断是否 != 0
0x9b iflt 判断是否 < 0
0x9c ifge 判断是否 >= 0
0x9d ifgt 判断是否 > 0
0x9e ifle 判断是否 <= 0

比较栈顶元素两个数据

  • i:int 整数
  • a:引用类型
指令 助记符 含义
0x9f if_icmpeq == 0
0xa0 if_icmpne != 0
0xa1 if_icmplt < 0
0xa2 if_icmpge >= 0
0xa3 if_icmpgt > 0
0xa4 if_icmple <= 0
0xa5 if_acmpeq ==
0xa6 if_acmpne !=

2.2.5、方法调用

绑定方式 调用方法 备注
invokestatic 静态 静态方法 通过类调用
即使通过对象调用,也会先将对象引用弹出操作数栈,再通过类调用。
invokespecial 静态 构造函数<init>、私有方法、超类方法 调用构造函数后会将对象引用出栈,因此执行之前需要 dup 复制引用
invokeinterface 动态 接口方法
invokevirtual 动态 虚方法,除了以上的方法 通常用于多态
invokedynamic 动态 JDK 1.7 动态类型语言
posted @ 2022-02-24 11:40  Jaywee  阅读(63)  评论(0编辑  收藏  举报

👇