[三] java虚拟机 JVM字节码 指令集 bytecode 操做码 指令分类用法 助记符

说明,本文的目的在于从宏观逻辑上介绍清楚绝大多数的字节码指令的含义以及分类
只要认真阅读本文必然可以对字节码指令集有所了解
若是须要了解清楚每个指令的具体详尽用法,请参阅虚拟机规范

指令简介

计算机指令就是指挥机器工做的指示和命令,程序就是一系列按必定顺序排列的指令,执行程序的过程就是计算机的工做过程。
一般一条指令包括两方面的内容: 操做码和操做数,操做码决定要完成的操做,操做数指参加运算的数据及其所在的单元地址。
虚拟机的字节码指令亦是如此含义
class文件至关于JVM的机器语言
class文件是源代码信息的完整表述
方法内的代码被保存到code属性中,字节码指令序列就是方法的调用过程
 
Java虚拟机的指令由一个字节长度的、表明着某种特定操做含义的操做码(opcode)
以及跟随其后的零至多个表明此操做所需参数的操做数(operand)所构成
虚拟机中许多指令并不包含操做数.只有一个操做码。
 
若是忽略异常处理,执行逻辑相似
do{
自动计算pc寄存器以及从pc寄存器的位置取出操做码;
if(存在操做数){
取出操做数;
}
执行操做码所定义的操做;
}while(处理下一次循环);
操做数的数量以及长度取决于操做码,若是一个操做数的长度超过了一个字节,那么它将大端排序存储,即高位在前的字节序。
例如,若是要将一个16位长度的无符号整数使用两个无符号字节存储起来(将它们命名为byte]和byte2 )
那这个16位无符号整数的值就是:  (bytel<<8) | byte2.
字节码指令流应当都是单字节对齐的,只有,tableswitch和lookupswitch两个指令例外 这俩货是4字节为单位的
 
限制了操做码长度为一个字节 0~255,   可是也就致使操做码个数不能超过256
放弃编译后代码的操做数对齐 也就省略不少填充和间隔符号
限制长度和放弃对齐也尽量的让编译后的代码短小精干
可是若是向上面那样若是操做码处理超过一个字节的数据时,就必须在运行时从字节流中重建出具体数据结构,将会有必定程度的性能损失

指令详解

说明:
操做码一个字节长度,也就是8位二进制数字,也就是两位十六进制数字
class文件只会出现数字形式的操做码
可是为了便于人识别,操做码有他对应的助记符形式
接下来全部的指令的说明,都是以助记符形式表达的
可是要明确,实际的执行运行并不存在助记符这些东西,都是根据操做码的值来执行
 
指令自己就是为了功能逻辑运算
运算天然要处理数据
因此说指令的设计是逻辑功能点与数据类型的结合
接下来先看下有哪些数据类型和逻辑功能点

数据类型

image_5b869c5d_c55
 
上一篇文章中已经说明JVM支持的数据类型
共有9中基本类型
对于基本类型  指令在设计的时候都用一个字母缩写来指代(boolean除外)
byte  short  int  long  float  double  char  reference boolean
b s i l f d c a
 

逻辑功能

加载存储指令
算数指令
类型转换指令
对象的建立于操做
操做数栈管理指令
控制转移指令
方法调用和返回指令
抛出异常
同步
 
指令基本上就是围绕着上面的逻辑功能以及数据类型进行设计的
固然  
也有一些并无明确用字母指代数据类型,好比arraylength 指令,并无表明数据类型的特殊字符,操做数只能是一个数组类型的对象
另外还有一些,好比无条件跳转指令goto 则是与数据类型无关的
 
接下来将会从各个维度对绝大多数指令进行介绍
注意: 在不一样的分类中,有些指令是重复的,由于有不少操做是须要处理数据的
也就是说数据类型相关的指令里面可能跟不少逻辑功能点相关联,好比 加载存储指令,能够加载int 能够加载long等
他在我接下来的说明中,可能不只仅会出如今数据类型相关的指令中
也会出如今加载存储指令的介绍中,请不要疑惑
就是要从多维度介绍这些指令,才能更好地理解他们

指令-相关计算机英语词汇含义

push push 按 推进 压入
load load 加载 装载 
const const 常数,不变的
store store 存储 保存到
add add 加法
sub subduction 减法
mul multiplication 乘法
div division 除法
inc increase 增长
rem remainder 取余 剩下的留下的
neg negate 取反 否认
sh shift 移位 移动变换
and and
or or
xor exclusive OR 异或
2 to 转换 转变 变成
cmp compare 比较
return return  返回
eq equal 相等
ne not equal 不相等
lt less than 小于
le less than or equal 小于等于
gt greater than 大于
ge greater than or equal 大于等于
if if 条件判断 若是
goto goto 跳转
invoke invoke 调用
dup dump 复制 拷贝 卸下 丢下
 

指令-数据类型相关的指令

java中的操做码长度只有个字节,因此必然,并不会全部的类型都有对应的操做
Java虚拟机指令集对于特定的操做只提供了有限的类型相关指令
有一些单独的指令能够再必要的时候用来将一些不支持的类型转换为可支持的类型
下表中最左边一列的T表示模板,只须要用数据类型的缩写,替换掉T 就能够获得对应的具体的指令
若是下表中为空,说明对这种数据类型不支持这种类型的操做
操做码/类型 byte short int long float double char reference
Tipush bipush sipush





Tconst

iconst lconst fconst dconst
aconst
Tload

iload lload fload dload
aload
Tstore

istore lstore fstore dstore
astore
Tinc

iinc




Taload  baload  saload  iaload  laload  faload  daload  caload  aaload
Tastore  bastore  sastore  iastore  lastore  fastore  dastore  castore  aastore
Tadd 

iadd  ladd  fadd  dadd

Tsub 

isub  lsub  fsub  dsub

Tmul 

imul lmul  fmul  dmul

Tdiv 

idiv  ldiv  fdiv  ddiv

Trem 

irem  lrem  frem  drem

Tneg 

ineg  lneg  fneg  dneg

Tshl

ishl lshl



Tshr

ishr lshr



Tushr 

iushr  lushr



Tand 

iand  land



Tor

ior  lor



Txor 

ixor  lxor



i2T  i2b  i2s 
i2l  i2f  i2d

l2T 

l2i 
l2f  l2d

f2T 

f2i  f2l 
f2d

d2T

d2i  d2l  d2f


Tcmp


lcmp



Tcmpl



fcmpl  dcmpl

Tcmpg 



fcmpg  dcmpg

if_TcmpOP

if_icmpOP 



if_acmpOP
Treturn

ireturn  lreturn  freturn  dreturn 
areturn
 
从上表的空白处能够看得出来
大部分数据类型相关联的指令,都没有支持整数类型 byte char short ,并且没有任何指令支持boolean类型
由于
编译器会在编译期或者运行期  将byte 和short 类型的数据 带符号扩展 为相应的int类型数据
相似的,boolean 和char类型数据零位扩展为相应的int类型数据
在处理boolean byte short char类型的数组时,也会转换为使用对应的int类型的字节码指令来处理
另外须要格外注意的是,上表是为了呈现部分与数据类型相关联的操做码
并非说全部的操做码都在上表中,仅仅是和数据类型相关联的才出如今了上表中
 
实际类型与运算类型的对应关系以下,分类后面会说到
实际类型 运算类型 分类
boolean int 1
int int 1
byte int 1
short int 1
int int 1
float float 1
reference reference 1
returnAddress returnAddress 1
long  long  2
double double 2
 

按照逻辑功能进行划分

加载存储指令

加载存储指令用于局部变量与操做数栈交换数据
以及常量装载到操做数栈
一、将一个局部变量加载到操做栈:
iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>
操做数为局部变量的位置序号 序号从0开始 , 局部变量以slot为单位分配的
将序号为操做数的局部变量slot 的值 加载到操做数栈
指令能够读做:将第(操做数+1)个 X(i l f d a)类型局部变量,推送至栈顶
ps: 操做数+1 是由于序号是从0开始的
 
二、将一个数值从操做数栈存储到局部变量表:
istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>
操做数为局部变量的位置序号 序号从0开始 , 局部变量以slot为单位分配的
将操做数栈的值保存到序号为操做数的局部变量slot中
指令能够读做:将栈顶 X(i l f d a)类型的数值 保存到  第(操做数+1)个 局部变量中
 
三、将一个常量加载到操做数栈:
bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m一、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>
操做数为将要操做的数值  或者常量池行号
指令能够读做:将类型X的值xxx 推送至栈顶  或者是 将 行号为xxx的常量推送至栈顶
 
四、扩充局部变量表的访问索引的指令:wide
 
形如  xxx_<n>以尖括号结尾的表明了一组指令 (例如 iload_<n>   表明了iload_0  iload_1  iload_2  iload_3)
这一组指令都是某个带有一个操做数的通用指令(例如 iload)的特殊形式
对于这些特殊形式来讲,他们表面上没有操做数,可是操做数隐含在指令里面了,除此以外,语义与原指令并无任何的不一样
(例如 iload_0  的语义与操做数为0时的iload 语义彻底相同)
<>尖括号中的字母表示了指令隐含操做数的数据类型
<n>表示非负整数  <i>表示int    <l> 表示long <f> float  <d> double  而byte char short类型数据常用int来表示
下划线 _   的后面紧跟着的值就是操做数
须要注意的是 _<n> 的形式不是无限的,对于load 和 store系列指令
对于超过4个,也就是第5个,也就是下标是4 日后
都是直接只用原始形式 iload 4  再也不使用_<n>的形式 因此你不会看到 load_4 load_5....或者store_4  store_5...

image_5b869c5d_3bf8
对于虚拟机执行方法来讲,操做数栈是工做区, 因此数据的流向是对于他  操做数栈   来讲的  
load就是局部变量数据加载到操做数栈 
store就是从操做数栈存储到局部变量表
对于常量只有加载到操做数栈进行使用,没有存储的说法,他也比较特殊
 
对于上图中的数据交换模型中,操做数栈是能够肯定的也是惟一的,栈就在那里,无论你见或不见
对于操做数栈与局部变量交换数据时,须要肯定的是  从 哪一个局部变量取数据 或者保存到哪一个局部变量中 
因此load 和 store的操做数都是局部变量的位置
 
对于操做数栈与常量交换数据,须要肯定的是到底加载哪一个值到操做数栈或者是从常量池哪行加载
因此加载常量到操做数栈的操做数 是 具体的数值 或者常量池行号
常量加载到操做数栈比较特殊单独说明
他根据<数据类型>以及<数据的取值范围>使用了不一样的方式

const指令
该系列命令主要负责把简单的数值类型送到栈顶
该系列命令不带参数。只把简单的数值类型送到栈顶时,才使用以下的命令。
好比对应int型该方式只能把-1,0,1,2,3,4,5(分别采用iconst_m1,iconst_0, iconst_1, iconst_2, iconst_3, iconst_4, iconst_5)
送到栈顶。对于int型,其余的数值请使用push系列命令(好比bipush)
指令码    助记符                            说明
0x01        aconst_null                 将null推送至栈顶
0x02         iconst_m1                   将int型(-1)推送至栈顶
0x03         iconst_0                      将int型(0)推送至栈顶
0x04         iconst_1                      将int型(1)推送至栈顶
0x05         iconst_2                      将int型(2)推送至栈顶
0x06         iconst_3                      将int型(3)推送至栈顶
0x07         iconst_4                      将int型(4)推送至栈顶
0x08         iconst_5                      将int型(5)推送至栈顶
0x09         lconst_0                      将long型(0)推送至栈顶
0x0a         lconst_1                      将long型(1)推送至栈顶
0x0b         fconst_0                     将float型(0)推送至栈顶
0x0c         fconst_1                      将float型(1)推送至栈顶
0x0d         fconst_2                     将float型(2)推送至栈顶
0x0e         dconst_0                     将double型(0)推送至栈顶
0x0f         dconst_1                    将double型(1)推送至栈顶
简言之 取值    -1~5 时,JVM采用const指令将常量压入栈中

push指令
该系列命令负责把一个整型数字(长度比较小)送到到栈顶。
该系列命令有一个参数,用于指定要送到栈顶的数字。
注意该系列命令只能操做必定范围内的整形数值,超出该范围的使用将使用ldc命令系列。
指令码        助记符                            说明
0x10          bipush    将单字节的常量值(-128~127)推送至栈顶
0x11           sipush    将一个短整型常量值(-32768~32767)推送至栈顶
 
ldc系列
该系列命令负责把数值常量或String常量值从常量池中推送至栈顶。
该命令后面须要给一个表示常量在常量池中位置(编号)的参数 也就是行号,
哪些常量是放在常量池呢?
好比:
final static int id=32768;   //32767+1 就不在sipush范围内了
final static float double=8.8
对于const系列命令和push系列命令操做范围以外的数值类型常量,都放在常量池中.
另外,全部不是经过new建立的String都是放在常量池中的
指令码    助记符                               说明
0x12          ldc                   将int, float或String型常量值从常量池中推送至栈顶
0x13          ldc_w               将int, float或String型常量值从常量池中推送至栈顶(宽索引)
0x14          ldc2_w             将long或double型常量值从常量池中推送至栈顶(宽索引)
ps:所谓宽索引是指常量池行号 索引的字段长度, ldc 的索引只有8位  ldc_w的索引则有16位
对于宽索引,指令格式为 ldc_w ,indexbyte1,indexbyte2  会计算  (indexbyte1<<8) | indexbyte2 来生成一个指向当前常量池的无符号16位索引
说白了就是寻址长度
简言之就是对于绝大多数的数值,都是存放在常量池中的 将须要使用ldc
对于一小部分可能比较经常使用的数值,则是能够直接把值当作操做数的 使用const 或者push
wide的含义   宽索引
字节码的指令是单字节的,对于局部变量来讲,最多容纳256个局部变量
wide指令就是用于扩展局部变量数的 ,将8位的索引在扩展8位 也就是16位 最多65536
形式为 
wide 要被扩展的操做码好比iload   操做数   (wide  iload 257 也就是  wide iload byte1  byte2)
iload操做码是做为wide 操做码的一个操做数来执行的
wide能够修饰 load  store  ret
若是wide修饰的是iinc 格式有些变化 
wide iinc  byte1 byte2 constbyte1 constbyte2  自己 iinc为 iinc  byte constbyte
扩展后的前两个字节16位为局部变量索引
后两个字节16位计算为 16位带符号的增量
计算的形式依旧是  (constbyte1 << 8) | constbyte2
 
 

算数指令

运算后的结果自动入栈
运算或算术指令用于对两个操做数栈上的值进行某种特定运算,并把结果从新存入到操做栈顶.
算术指令分为两种:整型运算的指令和浮点型运算的指令.
不管是哪一种算术指令,都使用Java虚拟机的数据类型
因为没有直接支持byte、short、char和boolean类型的算术指令,使用操做int类型的指令代替.
加法指令: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
比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp
再次强调
加add            减sub        乘mul        除div        求余rem        取反neg        移位sh     l r表示左右  
与and        或or        异或xor     自增inc       cmp比较
加 减 乘 除 求余 取反 支持 <int  i  long l   float  f   double d>   四种类型
理解点:经常使用操做支持四种经常使用类型  byte short char boolean使用int

移位运算与按位与或异或运算 支持< int  i  long l >
理解点: 移位与位运算支持整型,byte short char boolean使用int  另外还有long

自增支持< int  i >
补充说明:
关于移位运算, 
左移只有一种:
规则:丢弃最高位,往左移位,右边空出来的位置补0
右移有两种:
1. 逻辑右移:丢弃最低位,向右移位,左边空出来的位置补0
2. 算术右移:丢弃最低位,向右移位,左边空出来的位置补原来的符号位(即补最高位)
移位运算的u表示的正是逻辑移位

d 和f开头 分别表明double 和float的比较
cmpg 与cmpl 的惟一区别在于对NaN的处理,更多详细内容能够查看虚拟机规范的相关指令
lcmp 比较long类型的值

 

类型转换指令


类型转换指令能够将两种不一样的数值类型进行相互转换。
这些转换操做通常用于实现用户代码中的显式类型转换操做
或者用来解决字节码指令集不完备的问题
由于数据类型相关指令没法与数据类型一一对应的问题,好比byte short char boolean使用int,   因此必需要转换  
分为宽化 和 窄化
含义如字面含义,存储长度的变宽或者变窄
宽化也就是常说的安全转换,不会由于超过目标类型最大值丢失信息
窄化则意味着极可能会丢失信息
宽化指令和窄化指令的形式为  操做类型 2 (to)  目标类型  好比 i2l int 转换为long
宽化指令
int类型到long、float或者double类型
long类型到float、double类型
float类型到double类型
i2l、i2f、i2d
l2f 、l2d
f2d
窄化指令
int类型到byte short char类型
long类型到int类型
float类型到int或者long类型
从double类型到int long 或者float类型
i2b 、i2s 、i2c
l2i
f2i 、f2l
d2i 、d2l 、d2f
 

对象的建立与访问

实例和数组都是对象
可是Java虚拟机对类实例和数组的建立使用了不一样的字节码指令
涉及到对象的建立与访问的相关操做有:
1.建立实例对象/数组
2.访问实例变量和类变量
3.加载与存储,对于类实例属于引用类型存取使用加载存储指令,因此此处只有数组有相关操做了
4.还有一些附属信息 数组长度以及检查类实例或者数组类型
建立类实例 :   new
建立数组的指令 :
newarray  分配数据成员类型为基本数据类型的新数组
anewarray  分配数据成员类型为引用类型的新数组
multianewarray   分配新的多维数组
类变量声明的时候使用static关键字
访问与存储类中的静态字段也是使用static关键字
getstatic 从类中获取静态字段
putstatic 设置类中静态字段的值
普通的成员实例变量使用field指代
getfield 从对象中获取字段值
putfield 设置对象中的字段的值
访问与存储以前介绍过  使用的load 和store
数组也是对象 引用使用a来表示
因此对于数组的存取和访问指令    使用   类型+a+load 或者store 的形式
把一个数组元素加载到操做数栈的指令:
byte      char     short    int       long     float    double  reference   
对应的指令分别是
baload  caload  saload  iaload  laload  faload  daload  aaload

把一个操做数栈的值存储到数组元素中的指令:
byte       char      short     int        long      float     double   reference   
对应的指令分别是:
bastore  castore  sastore  iastore  lastore  fastore  dastore  aastore
 
获取数组长度的指令  arraylength
检查类实例或者数组类型的指令   instanceof  checkcast
 

操做数栈管理指令

操做数栈管理指令,顾名思义就是直接用于管理操做栈的
对于操做数栈的直接操做主要有 出栈/复制栈顶元素 / 以及 交换栈顶元素java

出栈,   分为将操做数栈栈顶的几个元素出栈,一个元素或者两个元素
pop表示出栈, 数值表明个数
pop pop2

交换 将栈顶端的两个数值进行交换
swap 
dup比较复杂一点
根本含义为复制栈顶的元素而后压入栈
不过涉及到复制几个元素,以及操做数栈的数据类型,因此比较复杂
 
上面提到过虚拟机处理的数据类型,有分类,分为1 和2两种类型
虚拟机能处理的类型long和double为类型2 其他为类型1 也就是int returnAddress  reference等
 
dup      复制操做数栈栈顶一个元素  而且将这个值压入到栈顶   value必须分类1
形式以下,右侧为栈顶
... , value
... , value , value
 
dup_x1 复制操做数栈栈顶的一个元素.并插入到栈顶如下  两个值以后   
形式以下,右侧为栈顶,value1 插入到了第二个元素value2 下面  value1 和value2  必须分类1
... , value2, value1
... , value1, value2, value1
 
dup_x2 复制操做数栈栈顶的一个元素. 并插入栈顶如下 2 个 或 3个值以后
形式一 若是 value3, value2, value1  全都是分类1  使用此形式  插入栈顶三个值 如下 也就是value3之下
..., value3, value2, value1 →
..., value1, value3, value2, value1
 
形式二若是value1 是分类1   value2 是分类2  那么使用此形式 插入栈顶两个值 如下,也就是value2 之下
..., value2, value1 →
..., value1, value2, value1
 
 
dup2  复制操做数栈栈顶一个或者两个元素,而且按照原有顺序,入栈到操做数栈
形式一 若是  value2, value1 全都是分类1  使用此形式 复制栈顶两个元素,按照原顺序,插入到栈顶
..., value2, value1 →
..., value2, value1, value2, value1
 
形式二 若是value 属于分类2 使用此形式 复制栈顶一个元素,插入到栈顶
..., value →
..., value, value
 
dup2_x1复制操做数栈栈顶一个或者两个元素,而且按照原有顺序   插入栈顶如下  两个或者三个 值  以后
形式一   若是  value3, value2, value1 都是分类1 使用此形式 复制两个元素,插入栈顶下 三个值以后,也就是value3 以后
..., value3, value2, value1 →
..., value2, value1, value3, value2, value1
 
形式二 若是value1 是分类2 value2 是分类1 使用此形式   复制一个元素,插入到栈顶如下 两个元素以后 
..., value2, value1 →
..., value1, value2, value1
 
 
dup_x2  复制操做数栈栈顶一个或者两个元素,而且按照原有顺序   插入栈顶如下  两个或者三个 或者四个   值  以后
 
形式一   全都是分类1  使用此形式  复制两个元素,插入到栈顶 第四个值后面
..., value4, value3, value2, value1 →
..., value2, value1, value4, value3, value2, value1
 
形式二 若是 value1 是分类2   value2 和 value3 是分类1 中的数据类型  使用此形式 复制一个元素 插入到栈顶 第三个值后面
..., value3, value2, value1 →
..., value1, value3, value2, value1
 
形式三 若是value 1  value2 是分类1   value3 是分类2 使用此形式 复制两个元素 插入到栈顶 第三个值后面
..., value3, value2, value1 →
..., value2, value1, value3, value2, value1
 
形式四 当value1 和value2 都是分类2 使用此形式  复制一个元素 插入到栈顶 第二个值后面
..., value2, value1 →
..., value1, value2, value1
上面关于dup的描述摘自 虚拟机规范,很难理解
看起来是很是难以理解的,不妨换一个角度
咱们知道局部变量的空间分配分为两种long 和 double 占用2个slot  其余占用一个
操做数栈,每一个单位能够表示虚拟机支持的任何的一个数据类型
不过操做数栈其实同局部变量同样,他也是被组织一个数组, 每一个元素的数据宽度和局部变量的宽度是一致的
因此对于long 和double占用2个单位长度  对于其余类型占用一个单位长度
虽然外部呈现上任何一个操做数栈能够表示任何一种数据类型,可是内部是有所区分的
如同局部变量表使用两个单位存储时,访问元素使用两个中索引小的那个相似的道理
因此能够把栈理解成线性的数组,
来一个long或者double 就分配两个单位空间做为一个元素
其他类型就分配一个单位空间做为元素

既然栈自己的结构中,线性空间的最小单位的数据宽度同局部变量,
long和double占用两个  也就是下面涉及说到的数据类型的分类1  和  分类2

假设栈的示意结构以下图所示,(只是给出来一种可能每一个元素的类型均可能是随机的)
左边表示呈现出来的栈元素 右边是内部的线性形式  咱们当作数组好了
image_5b869c5d_3c9b
对栈元素的处理,显然指的是对于栈元素内部数组的处理
因此天然要分为    
究竟是直接复制一个单位的数据        
仍是直接复制两个单位的数据 
 
 
一次复制占用一个单位空间   的指令 使用dup  
一次复制占用两个单位空间   的指令 使用dup2
 
一次复制占用一个单位空间 时 假设复制的栈顶是array[0] 
dup 能够理解为dup_x0    
插入到他栈顶的内部线性结构的第(1+0)个元素下面 因此array[0] 对应的必然是一个完整的栈元素 ,必然是分类1 不多是分类2的一半!
image_5b869c5d_24b7
dup_x1                           
插入到他栈顶的内部线性结构的第(1+1)个元素下面 也就是插到第二个下面  由于array[0] 对应value1为分类1  
若是接下来的是分类2的数据,必然接下来的两个单元array[1] 和array[2]是不可分割的,也就是不可能插入到array[1] 后面,因此array[1] 对应value2 也必须是分类1 也就是两个都是分类1
image_5b869c5d_5c27
 
dup_x2                          
插入到他栈顶的内部线性结构的第(1+2)个元素下面 也就是插到第三个后面,array[0] 对应value1为分类1 为分类1  
那么接下来的两个单位array[1] 和array[2],能够是一个分类2  也能够是两个分类1,都是能够的
image_5b869c5d_3651
 
image_5b869c5d_7212
 
一次复制占用两个单位的数据类型 时
dup2 能够理解为dup2_x0   
插入到他栈顶的内部线性结构的第(2+0)个元素下面 
这一次复制的两个单位array[0] 和 array[1],  到 array[1]下面  
多是对应value1 和value2 表示两个分类1  也多是对应一个value1 表示类型为分类2 
image_5b869c5d_3101
image_5b869c5d_6ad0
 
dup2_x1   插入到他栈顶的内部线性结构的第(2+1)个元素下面 也就是复制array[0] 和 array[1] 到第三个元素 array[2]的下面
array[0] 和 array[1] 可能分别对应value1 和value2 表示两个分类1 数据  也多是对应着一个value1表示一个分类2数据
可是array[2] 做为第三个单位,既然能被分割,天然他必须是分类1
因此要么三个都是分类1,要么value1 分类2  value2 分类1
image_5b869c5d_3201
image_5b869c5d_3543


dup2_x2  插入到他栈顶的内部线性结构的第(2+2)个元素下面 也就是复制array[0] 和 array[1] 到第四个内部元素 array[3]的下面
一次复制两个,放到第四个下面
这种情形下的组合就很是多了
全都是分类1的数据
image_5b869c5d_3cea

所有都是分类2
array[0]  和 array[1]  对应value1 表示一个分类2数据
array[2]  和 array[3]     对应value2 表示一个分类2数据
image_5b869c5d_5ddd

array[0]  和 array[1]  对应value1 表示一个分类2数据
array[2]  和 array[3]     对应value2 和 value3表示两个分类1数据
image_5b869c5d_4ebd

array[0]  和 array[1]  对应value1 和value2 表示两个分类1 数据
array[2]  和 array[3]    对应value3表示一个分类2数据
image_5b869c5d_4ea4

因此说只须要明确如下几点,就不难理解dup指令
操做数栈指令操做的是栈内部的存储单元,而不是以一个栈元素为单位的
long和double在栈元素内部须要两个存储单元,其他一个存储单元
两个相邻的内部单位组合起来表示一个栈元素时,是不能拆分的

再回过头看,全部的dup指令,不过是根据栈元素的实际存放的类型的排列组合,梳理出来的一些复制一个或者两个栈顶元素的实际操做方式而已
就是由于他是逆向推导的,因此看起来很差理解
 

控制转移指令

控制转移指令可让Java虚拟机有条件或者无条件的从指定的位置指令继续执行程序
而不是当前控制转移指令的下一条
控制转移指令包括
条件转移 复合条件转移以及无条件转移
 
boolean byte short char都是使用int类型的比较指令
long float   double 类型的条件分支比较,会先执行相应的比较运算指令,运算指令会返回一个整型数值到操做数栈中
随后在执行int类型的条件分支比较操做来完成整个分支跳转
 
显然,虚拟机会对int类型的支持最为丰富
全部的int类型的条件分支指令进行的都是有符号的比较
long float   double 类型的比较指令
lcmp
fcmpl   fcmpg
dcmpl   dcmpg
这五个都比较栈顶上面两个 指定类型的元素,而后将结果 [-1   0  1] 压入栈顶
cmpl与cmpg区别在于对NaN的处理,有兴趣的能够查看Java虚拟机规范
 
条件跳转指令
接下来这六个也就是上面说的配合long float 和double类型条件分支的比较
他们会对当前栈顶元素进行操做判断,只有栈顶的一个元素做为操做数
ifeq  当栈顶int类型元素    等于0时    ,跳转
ifne  当栈顶int类型元素    不等于0    时,跳转
iflt    当栈顶int类型元素    小于0    时,跳转
ifle    当栈顶int类型元素    小于等于0    时,跳转
ifgt   当栈顶int类型元素    大于0    时,跳转
ifge  当栈顶int类型元素    大于等于0    时,跳转
 
相似上面的long  float double 
int类型 和 reference  固然也有对两个操做数的比较指令,并且还一步到位了
if_icmpeq    比较栈顶两个int类型数值的大小 ,当前者  等于  后者时,跳转
if_icmpne    比较栈顶两个int类型数值的大小 ,当前者  不等于  后者时,跳转
if_icmplt      比较栈顶两个int类型数值的大小 ,当前者  小于  后者时,跳转
if_icmple    比较栈顶两个int类型数值的大小 ,当前者  小于等于  后者时,跳转
if_icmpge    比较栈顶两个int类型数值的大小 ,当前者  大于等于  后者时,跳转
if_icmpgt    比较栈顶两个int类型数值的大小 ,当前者  大于  后者时,跳转
if_acmpeq  比较栈顶两个引用类型数值的大小 ,当前者  等于  后者时,跳转
if_acmpne  比较栈顶两个引用类型数值的大小 ,当前者  不等于  后者时,跳转
复合条件跳转指令
tableswitch    switch 条件跳转 case值连续
lookupswitch  switch 条件跳转 case值不连续
无条件转移指令
goto 无条件跳转
goto_w 无条件跳转  宽索引
jsr   SE6以前 finally字句使用 跳转到指定16位的offset,并将jsr下一条指令地址压入栈顶
jsr_w SE6以前 同上  宽索引
ret  SE6以前返回由指定的局部变量所给出的指令地址(通常配合jsr  jsr_w使用)
w同局部变量的宽索引含义
 

方法调用和方法返回指令

方法调用和方法返回指令
方法调用分为
实例方法接口方法 调用父类私有实力初始化等特殊方法,类静态方法等
如下5条指令用于方法调用:
invokevirtual指令用于调用对象的实例方法
invokeinterface指令用于调用接口方法,它会在运行时搜索由特定对象所实现的这个接口方法,并找出适合的方法进行调用。
invokespecial指令用于调用一些须要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。
invokestatic指令用于调用类方法(static方法)
invokedynamic 调用动态连接方法  比较复杂,稍后有时间会专门讲解
 
方法的调用与数据类型无关
可是方法的返回指令根据返回值类型进行区分
ireturn      boolean byte char short int类型使用
lreturn long
freturn float
dreturn double
areturn reference
return  void方法 实例初始化方法(构造方法)   类和接口的类初始化方法
 

异常指令

异常处理指令
Java程序中显式抛出异常的操做  throw语句,都是由athrow 指令来实现的
除了throw语句显式的抛出异常状况以外,Java虚拟机规范还规定了许多运行时异常
会在其余Java虚拟机指令检测到异常状况时,自动抛出
 

同步指令

同步指令
同步一段指令集序列一般是由Java语言中的synchronized 语句块来表示的,
Java虚拟机的指令集中有monitorenter  monitorexit  (monitor  +enter/exit)

 
至此,虚拟机中的指令集的大体基本设计逻辑以及意图已经基本介绍清楚了,如须要更深一步的了解,请查看虚拟机规范  
相关文章
相关标签/搜索