[三] 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)

 
至此,虛擬機中的指令集的大體基本設計邏輯以及意圖已經基本介紹清楚了,如須要更深一步的瞭解,請查看虛擬機規範  
相關文章
相關標籤/搜索