- 1 解释执行与编译执行
- 2 基于栈/寄存器的指令集
- 3 解释器执行字节码过程
- 4 虚拟机进行数据运算的一般过程
1 解释执行与编译执行
- 解释执行:通过解释器执行
- 编译执行:通过即时编译器产生本地代码(二进制代码)执行
- 虚拟机的执行引擎支持以上两种方式
java语言的编译及执行过程:
C语言的编译过程:
(资料图片仅供参考)
2 基于栈/寄存器的指令集
- 基于栈的指令集架构:指令不带参数,使用操作数栈中的数据作为指令的运算输入,指令的运算结果也存储在操作数栈中。依赖操作数栈进行工作
- 基于寄存器的指令集架构:X-86指令集,指令带有操作数,依赖寄存器计算和存储。
- 字节码指令属于基于栈的指令集架构(部分指令会带有参数)
温馨提示:关于x86指令集在《深入理解计算机操作系统》读书笔记系列中有详细介绍
以计算1+1作为例子,体现两种指令集的差异
基于栈的指令集:
iconst_1 iconst_1 iadd istore_0
- 两条iconst_1指令连续把两个常量1压入栈
- iadd指令把栈顶的两个值出栈、相加,然后把结果放回栈顶
- 最后istore_0把栈顶的值放到局部变量表的第0个变量槽中
基于寄存器的指令集:
mov eax, 1 add eax, 1
- mov指令将立即数1放入eax寄存器
- add指令将eax寄存器的值加1,并把结果保存在eax寄存器中
栈的指令集的优缺点:
- 可移植,因为它不依赖寄存器(寄存器一般跟硬件体系挂钩)
- 指令相对更加简洁(寄存器指令集中还需要存放参数)
- 编译器实现更加简单(不需要考虑空间分配的问题,所需空间都在栈上操作)
- 缺点1:栈实现在内存中,比起寄存器的访存速度更慢。
- 缺点2:指令数量比寄存器架构来得更多(出栈、入栈操作本身就产生了相当大量的指令)
3 解释器执行字节码过程
例子说明Java虚拟机如何执行字节码
public class ByteCodeTest_3 { public int calc() { int a = 100; int b = 200; int c = 300; return (a + b) * c; }}
javac编译后的字节码完整内容如下:
minnesota-book:test howing.zhang$ javap -verbose ./ByteCodeTest_3.classClassfile /Users/howing.zhang/hy-Data/Work/study/project/mconcurrency/src/main/java/com/minnesota/practice/test/ByteCodeTest_3.class Last modified 2022-12-6; size 310 bytes MD5 checksum 542ed788c7ea7a11f0c7cd0cf046c2a6 Compiled from "ByteCodeTest_3.java"public class com.minnesota.practice.test.ByteCodeTest_3 minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPERConstant pool: #1 = Methodref #3.#12 // java/lang/Object."":()V #2 = Class #13 // com/minnesota/practice/test/ByteCodeTest_3 #3 = Class #14 // java/lang/Object #4 = Utf8 #5 = Utf8 ()V #6 = Utf8 Code #7 = Utf8 LineNumberTable #8 = Utf8 calc #9 = Utf8 ()I #10 = Utf8 SourceFile #11 = Utf8 ByteCodeTest_3.java #12 = NameAndType #4:#5 // "":()V #13 = Utf8 com/minnesota/practice/test/ByteCodeTest_3 #14 = Utf8 java/lang/Object{ public com.minnesota.practice.test.ByteCodeTest_3(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return LineNumberTable: line 3: 0 public int calc(); descriptor: ()I flags: ACC_PUBLIC Code: stack=2, locals=4, args_size=1 0: bipush 100 2: istore_1 3: sipush 200 6: istore_2 7: sipush 300 10: istore_3 11: iload_1 12: iload_2 13: iadd 14: iload_3 15: imul 16: ireturn LineNumberTable: line 5: 0 line 6: 3 line 7: 7 line 8: 11}SourceFile: "ByteCodeTest_3.java"
用图例来说明执行calc()方法过程中,操作数栈和局部变量表的变化情况:
由上图可知,calc()方法运行的栈帧结构中:
- 操作数栈的最大深度为2
- 本地变量表的变量槽最大使用数量为4
- 方法参数的个数为1,它是隐藏参数this
- 验证过程也满足:“stack=2, locals=4, args_size=1”该字节码内容
4 虚拟机进行数据运算的一般过程
java虚拟机进行数据运算,没有异常的情况下,遵从以下步骤:
- 先入栈:将常量或变量值写入操作数栈,push指令集负责。
- 出栈:将操作数栈数据保存到常量表,store指令集负责
- 入栈:数据从常量表复制操作数栈,为下一步的运算进行准备。load指令集负责
- 运算:对栈内(从栈顶开始寻找)数据进行运算,并把结果重新存入栈顶。运算指令集负责
- 返回:把计算结果(栈顶数据)返回给方法调用者。