网站qq弹窗,图片编辑器在线网页版,大气物流网站模块,网站建设方案.docJVM入门到入土-Java虚拟机寄存器指令集与栈指令集 HotSpot虚拟机中的任何操作都需要入栈和出栈的步骤。 由于跨平台性的设计#xff0c;Java的指令都是根据栈来设计的。不同平台CPU架构不同#xff0c;所以不能设计为基于寄存器的。优点是跨平台#xff0c;指令集小#x…JVM入门到入土-Java虚拟机寄存器指令集与栈指令集 HotSpot虚拟机中的任何操作都需要入栈和出栈的步骤。 由于跨平台性的设计Java的指令都是根据栈来设计的。不同平台CPU架构不同所以不能设计为基于寄存器的。优点是跨平台指令集小编译器容易实现缺点是性能下降实现同样的功能需要更多的指令。 参考资料
Java虚拟机规范JavaSE8深入理解Java虚拟机
JVM的两大指令集特点
基于栈式架构的特点
设计和实现更简单适用于资源受限的系统HotSpot虚拟机就基于此:
避开了寄存器的分配难题: 使用零地址指令方式分配。指令流中的指令大部分是零地址指令其执行过程依赖于操作栈。指令集更小编译器容易实现。不需要硬件支持可移植性更好更好实现跨平台
基于寄存器架构的特点
典型的应用是x86的二进制指令集: 比如传统的PC以及Android的Davlik虚拟机。
指令集架构则完全依赖硬件可移植性差性能优秀和执行更高效花费更少的指令去完成一项操作。在大部分情况下基于寄存器架构的指令集往往都以一地址指令、二地址指令和三地址指令为主而基于栈式架构的指令集却是以零地址指令为主。
获取栈指令集-指令javap
注意如果你使用JDK17可能会出现找不到控制台指令的问题因为默认安装不会配置该指令去JDK17的安装目录将bin添加到环境变量即可
先编译一个简单的12程序
public class StackOneTest {public static void main(String[] args) {int a 1;int b 2;int c ab;}
}然后使用javap -c class文件全名我们将在控制台得到如下内容
D:\CodeProjects\Eclipse\StackTest\bin\testjavajavap -c StackOneTest.class
Compiled from StackOneTest.java
public class testjava.StackOneTest {public testjava.StackOneTest();Code:0: aload_01: invokespecial #8 // Method java/lang/Object.init:()V4: returnpublic static void main(java.lang.String[]);Code:0: iconst_11: istore_12: iconst_23: istore_24: iload_15: iload_26: iadd7: istore_38: return
}由于手里没有基于寄存器的指令集实验环境可自行查阅查看方法 栈指令集字节码的语义
字节码指令集设计概述 在 Java 虚拟机的指令集中大多数的指令都包含了其所操作的数据类型信息。例如iload 指令用于从局部变量表中加载 int 类型的数据到操作数栈中而 ad 指加载的则是 float 类型的数据。这两个指令的操作可能会是由同一段代码来实现的但它们必须拥有各自独立的操作码。对于大部分与数据类型相关的字节码指令来说它们的操作码助记符中都有特殊的字符来表明该指令为哪种数据类型服务i 代表对 int 型的数据操作l 代表 longs 代表 shortb 代表 bytec 代表 charf 代表 floatd 代表 doublea 代表 reference。也有一些指令的助记符没有明确用字母指明数据类型例如 arraylength 指令它没有代表数据类型的特殊字符但操作数永远只能是一个数组类型的对象。还有另外一些指令例如无条件跳转指令 goto 则是与数据类型无关的。 因为 Java 虚拟机的操作码长度只有一个字节所以包含了数据类型的操作码给指令集的设计带来了很大的压力。如果每一种与数据类型相关的指令都支持 Java 虚拟机的所有运行时数据类型那恐怕就会超出一个字节所能表示的数量范围了。因此Java 虚拟机的指令集对于特定的操作只提供了有限的类型相关指令换句话说指令集将会故意设计成非完全独立的not orthogonal即并非每种数据类型和每一种操作都有对应的指令。有一些单独的指令可以在必要的时候用来将一些不支持的类型转换为可支持的类型。 表 2-2 列举了 Java 虚拟机所支持的字节码指令集。用数据类型列所代表的特殊字符替换 opcode 列的指令模板中的 T就可以得到一个具体的字节码指令。如果在表中指令模板与数据类型两列共同确定的单元格为空则说明虚拟机不支持对这种数据类型执行这项操作。例如load 指令有操作 int 类型的 iload但是没有操作 byte 类型的同类指令。请注意从表 2-2 中可以看出大部分的指令都没有支持整数类型 byte、char 和 short甚至没有任何指令支持 boolean 类型。编译器会在编译期或运行期将 byte 和 short 类型的数据带符号扩展sign-extend为相应的 int 类型数据将 boolean 和 char 类型数据零位扩展zero-extend为相应的 int 类型数据。与之类似在处理 boolean、byte、short 和 char 类型的数组时也会转换为使用对应的 int 类型的字节码指令来处理。因此操作数的实际类型为 boolean、byte、char 及 short 的大多数操作都可以用操作数的运算类型computational type为 int 的指令来完成。 from : Java虚拟机规范JavaSE8 附表 实际类型与关系映射表 加载与存储指令集
加载本地变量到操作数栈的指令:
指令描述iload将 int 类型加载到操作数栈iload n将指定索引的 int 类型加载到操作数栈lload_n将指定索引的 long 类型加载到操作数栈fload将 float 类型加载到操作数栈fload n将指定索引的 float 类型加载到操作数栈dload将 double 类型加载到操作数栈dload n将指定索引的 double 类型加载到操作数栈aload将引用类型加载到操作数栈aload n将指定索引的引用类型加载到操作数栈
存储操作数栈到局部变量表的指令:
指令描述istore将 int 类型存储到局部变量表istore n将 int 类型存储到指定索引的局部变量lstore n将 long 类型存储到指定索引的局部变量fstore将 float 类型存储到局部变量表fstore n将 float 类型存储到指定索引的局部变量dstore将 double 类型存储到局部变量表dstore n将 double 类型存储到指定索引的局部变量astore将引用类型存储到局部变量表astore_n将引用类型存储到指定索引的局部变量
加载常量到操作数栈的指令:
指令描述bipush将带符号的 byte 常量(-128~127)加载到操作数栈sipush将带符号的 short 常量(-32768~32767)加载到操作数栈ldc将 int, float 或 String 类型的常量加载到操作数栈ldc_w与 ldc 类似但用于更大的常量池索引ldc2_w将 long 或 double 类型的常量加载到操作数栈aconst_null将 null 加载到操作数栈iconst_m1将整数 -1 加载到操作数栈iconst i将整数常量加载到操作数栈lconst 1将长整数常量1加载到操作数栈fconst f将浮点数常量加载到操作数栈dconst d将双精度浮点数常量加载到操作数栈
扩充局部变量表的访问索引或立即数的指令:
指令描述wide扩展下一条指令使用的局部变量索引的宽度
算数指令集
算术指令:
操作类型指令加法iadd、ladd、fadd、dadd减法isub、Isub、fsub、dsub乘法imul、mul、fmul、dmul除法idiv、ldiv、fdiv、ddiv求余irem、Irem、frem、drem
逻辑位运算指令:
操作类型指令按位或ior、lor按位与iand、land按位异或ixor、lxor
其他运算指令:
操作类型指令求负值ineg、Ineg、fneg、dneg移位ishl、ishr、iushr、Ishl、Ishr、lushr局部变量自增iinc比较dcmpg、dcmpl、fcmpg、fcmpl、Icmp
栈指令集字节码分析
main方法中的算数字节码
D:\CodeProjects\Eclipse\StackTest\bin\testjavajavap -c StackOneTest.class
Compiled from StackOneTest.java
public class testjava.StackOneTest {public testjava.StackOneTest();Code:0: aload_01: invokespecial #8 // Method java/lang/Object.init:()V4: returnpublic static void main(java.lang.String[]);Code:0: iconst_11: istore_12: iconst_23: istore_24: iload_15: iload_26: iadd7: istore_38: return
}从上述字节码不难看出JVM先执行了StackOneTest对象的构造然后将这个对象变量加载到了操作数栈默认构造器我们重点来看main中的指令集
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2先将两个常量12加载到操作数栈并且进行了存储接下来
4: iload_1
5: iload_2
6: iadd
7: istore_3加载了两个变量ab到操作数栈然后使用算数指令进行相加最后进行存储结果这里的第三个变量未使用变量加载可能被编译器优化掉了我们稍加修改 可以发现在使用了第三个变量后JVM进行了正确的变量加载
含方法的字节码分析
我们增加一个方法来进一步分析
public class StackOneTest {public static int add(int a, int b) {return ab;}public static void main(String[] args) {int a 1;int b 2;int c add(a, b);System.out.println(c);}
}从这里可知 invokestatic应该为调用方法的指令集其运行也是和上一个大同小异不过需要注意不同返回值类型的Treturn指令T是借用泛型里的一个代号
多次入栈字节码分析
修改代码
public class StackOneTest {public static void main(String[] args) {int a 1;int b 2;int c 3;int d ab;int e ac;}
}相同方法进行分析 可以发现其字节码也是逐行进行操作的在变量de位置的处理和第一处也是类似的只不过a进行了重复入栈