其他分享
首页 > 其他分享> > 淮南办淮南证GA

淮南办淮南证GA

作者:互联网

YL办淮南证书(哪里)╋嶶(188)電(7384)呺(8713)哪里-(本地)办√各√种√证√-件︿Java 虚拟机(JVM)是执行已编译 Java 字节码的软件。它是 Java 平台的重要组成部分,包括程序、规范、库和数据结构,让它们协同工作。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 的整数按小端顺序存放)。

数据类型 所占字节 格式 数据类型 所占字节 格式
char 2 Unicode 字符 long 8 有符号整数
byte 1 有符号整数 float 4 IEEE 单精度实数
short 2 有符号整数 double 8 IEEE 双精度实数
int 4 有符号整数
2) 比较指令
比较指令从操作数栈的顶端弹出两个操作数,对它们进行比较,再把比较结果压入堆栈。现在假设操作数入栈顺序如下所示:

 

下表给出了比较 op1 和 op2 之后压入堆栈的数值:

op1 和 op2 比较的结果 压入操作数栈的数值
op1 > op2 1
op1 = op2 0
op1 < op2 -1
dcmp 指令比较双字,fcmp 指令比较浮点数。
3) 分支指令
分支指令可以分为有条件分支和无条件分支。Java 字节码中无条件分支的例子是 goto 和 jsr。

goto 指令无条件分支到一个标号:
goto label

jsr 指令调用用标号定义的子程序。其语法如下:
jsr label

条件分支指令通常检测从操作数栈顶弹出的数值。根据该值,指令决定是否分支到给定标号。比如,ifle 指令就是当弹出数值小于等于 0 时跳转到标号。其语法如下:
ifle label

同样,ifgt 指令就是当弹出数值大于等于 0 时跳转到标号。其语法如下:
ifgt label

Java 反汇编示例
为了帮助理解 Java 字节码是如何工作的,本节将给出用 Java 编写的一些短代码例子。在这些例子中,请注意不同版本 Java 的字节码清单细节会存在些许差异。

每个编号行表示一条 Java 字节码指令的字节偏移量。本例中,可以发现每条指令都只占一个字节,因为指令偏移量的编号是连续的。

尽管字节码反汇编一般不包括注释,这里还是会将注释添加上去。虽然局部变量在运行时堆栈中有专门的保留区域,但是指令在执行算术运算和数据传送时还会使用另一个堆栈,即操作数栈。为了避免在这两个堆栈间产生混淆,将用索引值来指代变量位置,如 0、1、2 等。

现在来仔细分析刚才的字节码。开始的两条指令将一个常数值压入操作数栈,并把同一个值弹出到位置为 0 的局部变量:
iconst_3 //常数(3)压入操作数栈
istore_0 //弹出到局部变量0

接下来的四行将其他两个常数压入操作数栈,并把它们弹岀到位置分别为 1 和 2 的局部变量:
iconst_2 //常数(2)压入操作数栈
istore_1 //弹出到局部变量1
iconst_0 //常数(0)压入操作数栈
istore_2 //弹出到局部变量2

由于已经知道了该生成字节码的 Java 源代码,因此,很明显下表列出的是三个变量的位置索引:

位置索引 变量名
0 A
1 B
2 sum
接下来,为了实现加法,必须将两个操作数压入操作数栈。指令 iload_0 将变量 A 入栈,指令 iload_1 对变量 B 进行相同的操作:
iload_0 // (A 入栈)
iload_1 // (B 入栈)

现在操作数栈包含两个数:

 

这里并不关心这些例子的实际机器表示,因此上图中的运行时堆栈是向上生长的。每个堆栈示意图中的最大值即为栈顶。

指令 iadd 将栈顶的两个数相加,并把和数压入堆栈:
iadd

操作数栈现在包含的是 A、B 的和数:

 

指令 istore_2 将栈顶内容弹出到位置为 2 的变量,其变量名为 sum:
istore_2

操作数栈现在为空。

【示例 2】两个 Double 类型数据相加

下面的 Java 代码片段实现两个 double 类型的变量相加,并将和数保存到 sum。它执行的操作与两个整数相加示例相同,因此这里主要关注的是整数处理与 double 处理的差异:
double A = 3.1;
double B = 2;
double sum = A + B;
本例的反汇编字节码如下所示,用 javap 实用程序可以在右边插入注释:
ldc2_w #20; // double 3.Id
dstore_0
ldc2_w #22; // double 2.Od
dstore_2
dload_0
dload_2
dadd
dstore_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.Od
dstore_2 // 保存到 B

指令 dload_0 和 dload_2 把局部变量入栈,其索引指的是 64 位位置(两个变量栈项),因为双字数值要占用 8 个字节:
dload_0
dload_2

接下来的指令(dadd)将栈顶的两个 double 值相加,并把和数入栈:
dadd

最后,指令 dstore_4 把栈顶内容弹出到位置为 4 的局部变量:
dstore_4

JVM 条件分支
了解 JVM 怎样处理条件分支是理解 Java 字节码的重要一环。比较操作总是从堆栈栈顶弹出两个数据,对它们进行比较后,再把结果数值入栈。条件分支指令常常跟在比较操作的后面,利用栈顶数值决定是否分支到目标标号。比如,下面的 Java 代码包含一个简单的 IF 语句,它将两个数值中的一个分配给一个布尔变量:
double A = 3.0;
boolean result = false;
if( A > 2.0 )
result = false;
else
result = true;
该 Java 代码对应的反汇编如下所示:
ldc2_w #26; // double 3.Od
dstore_0 // 弹出到 A
iconst_0 // false = 0
istore_2 //保存到 result
dload_0
ldc2_w #22; // double 2.0d
dcmpl
ifle 19 //如果 A ≤ 2.0,转到 19
iconst_0 // false
istore_2 // result = false
goto 21 //跳过后面两条语句
iconst_l // true
istore_2 // result = true
开始的两条指令将 3.0 从常数池复制到运行时堆栈,再把它从堆栈弹岀到变量 A:
ldc2_w #26; // double 3.0d
dstore_0 // 弹出至A

接下来的两条指令将布尔值 false (等于 0)从常量区复制到堆栈,再把它弹出到变量 result:
iconst_0 // false = 0
istore_2 // 保存到 result

A 的值(位置 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 源代码示例,若 A>2.0,其分配的值为 false:
if( A > 2.0 )
result = false;
else
result = true;
如果 A <= 2.0,Java 字节码就把 IF 语句转向偏移量为 19 的语句,为 result 分配数值 true。与此同时,如果不发生到偏移量 19 的分支,则由下面几条指令把 false 赋给 result:
iconst_0 // false
istore_2 // result = false
goto 21 //跳过后面两条指令

偏移量 16 的指令 goto 跳过后面两行代码,它们的作用是给 result 分配 true:
iconst_l // true
istore_2 // result = true

Java 虚拟机的指令集与 x86 处理器系列的指令集有很大的不同。它采用面向堆栈的方法实现计算、比较和分支,与 x86 指令经常使用寄存器和内存操作数形成了鲜明的对比。

虽然字节码的符号反汇编不如 x86 汇编语言简单,但是,编译器生成字节码也是相当容易的。每个操作都是原子的,这就意味着它只执行一个操作。

标签:操作数,Java,字节,double,指令,GA,堆栈,淮南
来源: https://www.cnblogs.com/bianweibk/p/14506824.html