- 字節(jié)碼與數(shù)據(jù)類型
在Java虛擬機的指令集中,大多數(shù)的指令都包含了操作所對應的數(shù)據(jù)類型信息蓉坎。比如iload指令表示從局部變量表中加載int型數(shù)據(jù)到操作數(shù)棧中澳眷,而fload表示加載float類型的數(shù)據(jù)。不過袍嬉,這兩條指令再虛擬機的內(nèi)部可能是由同一段代碼來實現(xiàn)的境蔼,但在class文件中必須有自己的操作碼灶平。
我們已經(jīng)知道Java指令的長度只有一個字節(jié)伺通,這就限制了指令集的大小。如果每個指令都像上面兩個指令那樣包含所有的數(shù)據(jù)類型逢享,那么就有可能導致指令過多罐监。因此,Java虛擬機的指令集對于特定的操作只提供了有限的類型相關指令去支持它瞒爬。比如弓柱,大多數(shù)指令沒有支持整數(shù)類型byte、char和short侧但,甚至沒有指令支持boolean類型矢空。
這些指令中都有特殊的字符來表示專門支持的類型:i代表int類型,l代表long禀横,s代表short屁药,b代表byte,c代表char柏锄,f代表float酿箭,d代表double缭嫡,a代表Reference妇蛀。
這里僅僅介紹一下指令的種類以及作用,并不會過多的介紹各個指令的含義以及使用有勾,需要的話可以查看《Java虛擬機規(guī)范(Java SE 7版)》蔼卡。
- 加載和存儲指令
加載指令用于將局部變量表中的數(shù)據(jù)傳送到操作數(shù)棧中雇逞,而存儲指令用于將操作數(shù)棧中的結果傳送到局部變量表中茁裙。這類指令包括如下幾種:
將一個局部變量加載到操作棧晤锥,比如iload矾瘾、iload、fload蛉迹、fload放妈、lload、lload珍策、dload宅倒、dload唉堪、aload、aload链方;
將一個數(shù)值從操作數(shù)棧存儲到局部變量表祟蚀,比如istore前酿、istore罢维、lstore肺孵、lstore、fstore吓肋、fstore瑰艘、dstore紫新、dstore弊琴、astore敲董、astore;
將一個常量加載到操作數(shù)棧腋寨,比如bipush、sipush化焕、ldc、ldc_w撒桨、ldc2w查刻、aconst_null凤类、iconst_ml穗泵、iconst、lconst_谜疤、fconst_现诀、dconst_仔沿;
擴充局部變量表的訪問索引的指令:wide;
上面中帶尖括號的指令實際是一組指令。比如iload烘浦,代表了iload_1、iload_2和iload_3闷叉。這幾組指令是某個帶操作數(shù)的指令(比如iload)的特殊形式握侧,它們省略了操作數(shù),不過操作數(shù)隱含在指令中嘿期。
- 運算指令
運算或算術指令用于對一個或兩個操作數(shù)棧上的值進行某種特定的運算品擎,并將結果存入棧頂。大體上可以分為兩種备徐,對整數(shù)進行運算的指令和對浮點數(shù)進行運算的指令萄传。不過,由于沒有支持byte蜜猾、short秀菱、char和boolean的算術指令,對于這些數(shù)據(jù)的運算蹭睡,會把它們轉化為int類型進行運算衍菱。指令列出如下:
<pre>
加法指令: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霞扬;
</pre>
- 類型轉換指令
類型轉換指令用來將兩種不同類型進行轉換,這些轉換操作一般用于實現(xiàn)代碼中的顯示類型轉換操作枫振,或者前面提到的字節(jié)碼指令集中數(shù)據(jù)類型相關指令無法與數(shù)據(jù)類型一一對應的問題喻圃。
虛擬機直接支持寬化類型轉換,即小范圍類型向大范圍類型的安全轉換粪滤,不需要顯示的轉換指令斧拍。
但是處理窄化類型轉換時,必須顯示使用轉換指令來完成杖小,這些指令包括:i2b肆汹、i2c愚墓、i2s、l2i昂勉、f2l浪册、d2i、d2l和d2f岗照。這些指令可能會導致數(shù)值的精度丟失村象。
- 對象創(chuàng)建與訪問指令
雖然類實例和數(shù)組都是對象,但是虛擬機創(chuàng)建類對象和數(shù)組的指令是不同的攒至。對象創(chuàng)建后厚者,就可以通過對象訪問指令獲取對象實例或者數(shù)組實例中的字段或數(shù)組元素,指令如下:
創(chuàng)建類實例的指令:new迫吐;
創(chuàng)建數(shù)組的指令:newarray库菲、anewarray、multianewarray志膀;
訪問類字段和實例字段的指令:getfield蝙昙、putfield、getstatic梧却、putstatic奇颠;
把一個數(shù)組元素加載到操作數(shù)棧的指令:baload、caload放航、saload烈拒、iaload、laload广鳍、faload荆几、daload、aaload赊时;
將一個操作數(shù)棧的值存儲到數(shù)組元素中的指令:bastore吨铸、castore、sastore祖秒、iastore诞吱、fastore、dastore竭缝、aastore房维;
取數(shù)組長度的指令:arraylength;
檢查類實例類型的指令:instanceof抬纸、checkcast咙俩;
- 操作數(shù)棧管理指令
就像操作一個普通的棧一樣耘擂,Java虛擬機提供了一些用于直接操作操作數(shù)棧的指令镇匀,包括:
將操作數(shù)棧的棧頂一個或兩個元素出棧:pop、pop2;
復制棧頂一個或兩個數(shù)值并將復制值或雙份的復制值重新壓入棧頂:dup颠悬、dup2谐宙、dup_x1拇派、dup2_x1后室、dup_x2、dup2_x2独撇;
將棧頂最頂端的兩個數(shù)值互換:swap屑墨;
- 控制轉移指令
控制轉移指令可以讓Java虛擬機有條件或無條件的從指定的位置指令而不是控制轉移指令的下一條指令繼續(xù)執(zhí)行,可以理解為控制轉移指令改變了PC寄存器的值纷铣。指令如下:
條件分支:ifeq卵史、iflt、ifle搜立、ifgt以躯、ifge、ifnonnull啄踊、if_icmpeq忧设、if_icmpne、if_icmplt颠通、if_icmpgt址晕、、if_icmpge顿锰、if_acmpeq和if_acmpne谨垃;
復合條件分支:tableswitch、lookupswitch硼控;
無條件分支:goto刘陶、goto_w、jsr牢撼、jsr_w匙隔、ret;
- 方法調用和返回指令
這里僅僅列出5條用于方法調用的指令:
invokevirtual指令用于調用對象的實例方法熏版,根據(jù)對象的實際類型進行分派(虛方法分派)纷责,這也是Java語言中最常見的方法分派方式;
invokeinterface指令用于調用接口方法纳决,它會在運行時搜索一個實現(xiàn)了這個接口方法的對象碰逸,找出適合的方法進行調用;
invokespecial指令用于調用一些需要特殊處理的實例方法阔加,包括實例初始化方法、私有方法和父類方法满钟;
invokestatic指令用于調用類方法(static方法)胜榔;
invokedynamic指令用于在運行時動態(tài)解析出調用點限定符索引用的方法胳喷,并執(zhí)行方法,前面4條指令的分派邏輯都固化在Java虛擬機內(nèi)部夭织,而invokedynamic指令的分派邏輯是由用戶所設定的引導方法決定的吭露;
方法調用指令與類型無關,但是方法返回指令是根據(jù)返回值的類型區(qū)分的尊惰,包括ireturn讲竿、lreturn、freturn弄屡、dreturn和areturn题禀,另外還有一個return指令供聲明為void的方法、實例初始化方法以及類和接口的類初始化方法使用膀捷。
- 異常處理指令
在Java程序中顯式拋出異常的操作(throw語句)都是由athrow指令來實現(xiàn)的迈嘹,除了用throw語句顯式拋出異常外,Java虛擬機規(guī)范還規(guī)定了許多運行時異常會在其他Java虛擬機指令檢測到異常狀況時自動拋出全庸。
而在Java虛擬機中秀仲,處理異常(catch語句)不是由字節(jié)碼指令來完成的,而是采用異常表來完成的壶笼。
- 同步指令
Java虛擬機可以支持方法級的同步和方法內(nèi)部一段指令序列的同步神僵,這兩種同步結構都是使用管程(Monitor)來支持的。
方法級的同步是隱式的覆劈,即不需要通過字節(jié)碼指令來控制挑豌,它實現(xiàn)在方法調用和返回操作中。虛擬機可以從方法常量池的方法表結構中的ACC_SYNCHRONIZED訪問標志得知一個方法是否聲明為同步方法墩崩。當方法調用時氓英,調用指令就會去檢查方法的ACC_SYNCHRONIZED訪問標志是否被設置了,如果設置鹦筹,執(zhí)行線程就要求持有管程铝阐。在方法執(zhí)行期間,執(zhí)行線程持有了管程铐拐,其他任何線程都無法再獲取到同一個管程徘键。如果一個方法在執(zhí)行期間發(fā)生了異常,并在方法中無法處理此異常遍蟋,那么這個同步方法所持有的管程將在異常拋出后自動釋放吹害。
同步一段指令集序列通常是由Java語言中的synchronized語句塊表示的,Java虛擬機的指令集中有monitorenter和monitorexit指令來支持synchronized關鍵字的語義虚青。正確實現(xiàn)synchronized關鍵字需要Javac編譯器和Java虛擬機兩者共同協(xié)作它呀。編譯器必須保證每個monitorenter指令都有對應的monitorexit指令。
參考:http://cyw3.github.io/YalesonChan/2016/Java-Byte-Code.html#4.1