华云电力建设监理公司网站,asp做的手机网站,app开发工具手机版,wordpress顶部菜单怎么删虽然本教程的内容为 x86 处理器的原生汇编语言#xff0c;但是了解其他机器架构如何工作也是有益的。JVM 是基于堆栈机器的首选示例。JVM 用堆栈实现数据传送、算术运算、比较和分支操作#xff0c;而不是用寄存器来保存操作数(如同 x86 一样)。数据结构#xff0c;让它们协…虽然本教程的内容为 x86 处理器的原生汇编语言但是了解其他机器架构如何工作也是有益的。JVM 是基于堆栈机器的首选示例。JVM 用堆栈实现数据传送、算术运算、比较和分支操作而不是用寄存器来保存操作数(如同 x86 一样)。数据结构让它们协同工作。Java 字节码是指编译好的 Java 程序中使用的机器语言的名字。JVM 执行的编译程序包含了 Java 字节码。每个 Java 源程序都必须编译为 Java 字节码(形式为 .class 文件)后才能执行。包含 Java 字节码的程序可以在任何安装了 Java 运行时软件的计算机系统上执行。例如一个 Java 源文件名为 Account.java编译为文件 Account.class。这个类文件是该类中每个方法的字节码流。JVM 可能选择实时编译(just-in-time compilation)技术把类字节码编译为计算机的本机机器语言。正在执行的 Java 方法有自己的堆栈帧存放局部变量、操作数栈、输入参数、返回地址和返回值。操作数区实际位于堆栈顶端因此压入这个区域的数值可以作为算术和逻辑运算的操作数以及传递给类方法的参数。在局部变量被算术运算指令或比较指令使用之前它们必须被压入堆栈帧的操作数区域。通常把这个区域称为操作数栈(operand stack)。Java 字节码中每条指令包含 1 字节的操作码、零个或多个操作数。操作码可以用 Java 反汇编工具显示名字如 iload、istore、imul 和 goto。每个堆栈项为 4 字节(32 位)。查看反汇编字节码Java 开发工具包(JDK)中的工具 javap.exe 可以显示 java.class 文件的字节码这个操作被称为文件的反汇编。命令行语法如下所示javap -c classname比如若类文件名为 Account.class则相应的 javap 命令行为javap -c Account安装 Java 开发工具包后可以在 \bin 文件夹下找到 javap.exe 工具。指令集1) 基本数据类型JVM 可以识别 7 种基本数据类型如下表所示。和 x86 整数一样所有有符号整数都是二进制补码形式。但它们是按照大端顺序存放的即高位字节位于每个整数的起始地址(x86 的整数按小端顺序存放)。数据类型所占字节格式数据类型所占字节格式char2Unicode 字符long8有符号整数byte1有符号整数float4IEEE 单精度实数short2有符号整数double8IEEE 双精度实数int4有符号整数2) 比较指令比较指令从操作数栈的顶端弹出两个操作数对它们进行比较再把比较结果压入堆栈。现在假设操作数入栈顺序如下所示下表给出了比较 op1 和 op2 之后压入堆栈的数值op1 和 op2 比较的结果压入操作数栈的数值op1 op21op1 op20op1 op2-1dcmp 指令比较双字fcmp 指令比较浮点数。3) 分支指令分支指令可以分为有条件分支和无条件分支。Java 字节码中无条件分支的例子是 goto 和 jsr。goto 指令无条件分支到一个标号goto labeljsr 指令调用用标号定义的子程序。其语法如下jsr label条件分支指令通常检测从操作数栈顶弹出的数值。根据该值指令决定是否分支到给定标号。比如ifle 指令就是当弹出数值小于等于 0 时跳转到标号。其语法如下ifle label同样ifgt 指令就是当弹出数值大于等于 0 时跳转到标号。其语法如下ifgt labelJava 反汇编示例为了帮助理解 Java 字节码是如何工作的本节将给出用 Java 编写的一些短代码例子。在这些例子中请注意不同版本 Java 的字节码清单细节会存在些许差异。【示例 1】两个整数相加下面的 Java 源代码行实现两个整数相加并将和数保存在第三个变量中int A 3;int B 2;int sum 0;sum A B;该 Java 代码的反汇编如下iconst_3istore_0iconst_2istore_liconst_0istore_2iload_0iload_liaddistore_2每个编号行表示一条 Java 字节码指令的字节偏移量。本例中可以发现每条指令都只占一个字节因为指令偏移量的编号是连续的。尽管字节码反汇编一般不包括注释这里还是会将注释添加上去。虽然局部变量在运行时堆栈中有专门的保留区域但是指令在执行算术运算和数据传送时还会使用另一个堆栈即操作数栈。为了避免在这两个堆栈间产生混淆将用索引值来指代变量位置如 0、1、2 等。现在来仔细分析刚才的字节码。开始的两条指令将一个常数值压入操作数栈并把同一个值弹出到位置为 0 的局部变量iconst_3 //常数(3)压入操作数栈istore_0 //弹出到局部变量0接下来的四行将其他两个常数压入操作数栈并把它们弹岀到位置分别为 1 和 2 的局部变量iconst_2 //常数(2)压入操作数栈istore_1 //弹出到局部变量1iconst_0 //常数(0)压入操作数栈istore_2 //弹出到局部变量2由于已经知道了该生成字节码的 Java 源代码因此很明显下表列出的是三个变量的位置索引位置索引变量名0A1B2sum接下来为了实现加法必须将两个操作数压入操作数栈。指令 iload_0 将变量 A 入栈指令 iload_1 对变量 B 进行相同的操作iload_0 // (A 入栈)iload_1 // (B 入栈)现在操作数栈包含两个数这里并不关心这些例子的实际机器表示因此上图中的运行时堆栈是向上生长的。每个堆栈示意图中的最大值即为栈顶。指令 iadd 将栈顶的两个数相加并把和数压入堆栈iadd操作数栈现在包含的是 A、B 的和数指令 istore_2 将栈顶内容弹出到位置为 2 的变量其变量名为 sumistore_2操作数栈现在为空。【示例 2】两个 Double 类型数据相加下面的 Java 代码片段实现两个 double 类型的变量相加并将和数保存到 sum。它执行的操作与两个整数相加示例相同因此这里主要关注的是整数处理与 double 处理的差异double A 3.1;double B 2;double sum A B;本例的反汇编字节码如下所示用 javap 实用程序可以在右边插入注释ldc2_w #20; // double 3.Iddstore_0ldc2_w #22; // double 2.Oddstore_2dload_0dload_2dadddstore_4下面对这个代码进行分步讨论。偏移量为 0 的指令 ldc2_w 把一个浮点常数(3.1)从常数池压入操作数栈。ldc2 指令总是用两个字节作为常数池区域的索引ldc2_w #20; // double 3.ld偏移量为 3 的 dstore 指令从堆栈弹出一个 double 数送入位置为 0 的局部变量。该指令起始偏移量(3)反映出第一条指令占用的字节数(操作码加上两字节索引)dstore_0 //保存到 A同样接下来偏移量为 4 和 7 的两条指令对变量 B 进行初始化ldc2_w #22; // double 2.Oddstore_2 // 保存到 B指令 dload_0 和 dload_2 把局部变量入栈其索引指的是 64 位位置(两个变量栈项)因为双字数值要占用 8 个字节dload_0dload_2接下来的指令(dadd)将栈顶的两个 double 值相加并把和数入栈dadd最后指令 dstore_4 把栈顶内容弹出到位置为 4 的局部变量dstore_4JVM 条件分支了解 JVM 怎样处理条件分支是理解 Java 字节码的重要一环。比较操作总是从堆栈栈顶弹出两个数据对它们进行比较后再把结果数值入栈。条件分支指令常常跟在比较操作的后面利用栈顶数值决定是否分支到目标标号。比如下面的 Java 代码包含一个简单的 IF 语句它将两个数值中的一个分配给一个布尔变量double A 3.0;boolean result false;if( A 2.0 )result false;elseresult true;该 Java 代码对应的反汇编如下所示ldc2_w #26; // double 3.Oddstore_0 // 弹出到 Aiconst_0 // false 0istore_2 //保存到 resultdload_0ldc2_w #22; // double 2.0ddcmplifle 19 //如果 A ≤ 2.0,转到 19iconst_0 // falseistore_2 // result falsegoto 21 //跳过后面两条语句iconst_l // trueistore_2 // result true开始的两条指令将 3.0 从常数池复制到运行时堆栈再把它从堆栈弹岀到变量 Aldc2_w #26; // double 3.0ddstore_0 // 弹出至A接下来的两条指令将布尔值 false (等于 0)从常量区复制到堆栈再把它弹出到变量 resulticonst_0 // false 0istore_2 // 保存到 resultA 的值(位置 0)压入操作数栈数值 2.0 紧跟其后入栈dload_0 //A 入栈ldc2_w #22; // double 2.0d操作数栈现在有两个数值指令 dcmpl 将两个 double 数弹出堆栈进行比较。由于栈顶的数值(2.0)小于它下面的数值(3.0)因此整数 1 被压入堆栈。dcmpl如果从堆栈弹出的数值小于等于 0则指令 ifle 就分支到给定的偏移量ifle 19 //如果 stack.pop() 0转到 19这里要回顾一下之前给出的 Java 源代码示例若 A2.0其分配的值为 falseif( A 2.0 )result false;elseresult true;如果 A 2.0Java 字节码就把 IF 语句转向偏移量为 19 的语句为 result 分配数值 true。与此同时如果不发生到偏移量 19 的分支则由下面几条指令把 false 赋给 resulticonst_0 // falseistore_2 // result falsegoto 21 //跳过后面两条指令偏移量 16 的指令 goto 跳过后面两行代码它们的作用是给 result 分配 trueiconst_l // trueistore_2 // result trueJava 虚拟机的指令集与 x86 处理器系列的指令集有很大的不同。它采用面向堆栈的方法实现计算、比较和分支与 x86 指令经常使用寄存器和内存操作数形成了鲜明的对比。虽然字节码的符号反汇编不如 x86 汇编语言简单但是编译器生成字节码也是相当容易的。每个操作都是原子的这就意味着它只执行一个操作。若 JVM 使用的是实时编译器则 Java 字节码只要在执行前转换为本地机器语言即可。就这方面来说Java 字节码与基于精简指令集(RISC)模型的机器语言有很多共同点。