Java字節(jié)碼指令解析

字節(jié)碼與數(shù)據(jù)類型

在Java虛擬機(jī)的指令集中,大多數(shù)的指令都包含了其操作所對(duì)應(yīng)的數(shù)據(jù)類型信息。

加載和存儲(chǔ)指令

加載和存儲(chǔ)指令用于將數(shù)據(jù)在幀棧中的局部變量表和操作數(shù)棧之間來(lái)回傳輸。這些指令包括如下內(nèi)容:
1:將一個(gè)局部變量加載到操作棧:iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>。
2:將一個(gè)數(shù)值從操作數(shù)棧存儲(chǔ)到局部變量表:istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>;
3:將一個(gè)常量加載到操作數(shù)棧:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_ml、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>;
4:擴(kuò)充局部變量表的訪問(wèn)索引指令:wide。

存儲(chǔ)數(shù)據(jù)的操作數(shù)棧和局部變量表主要就是由加載和存儲(chǔ)指令進(jìn)行操作,除此之外,還有少量指令,如訪問(wèn)對(duì)象的字段或數(shù)組元素的指令也會(huì)向操作數(shù)棧傳輸數(shù)據(jù)。

注意:上面類似這樣的格式:iload_<n>:代表了iload_0、iload_1、iload_2和iload_3這幾條指令

運(yùn)算指令

運(yùn)算或算數(shù)指令用于對(duì)兩個(gè)操作數(shù)棧上的值進(jìn)行某種特定運(yùn)算,并把結(jié)果重新存入到操作棧頂。大體上算數(shù)指令可以分為兩種:對(duì)整型數(shù)據(jù)進(jìn)行運(yùn)算的指令和對(duì)浮點(diǎn)型數(shù)據(jù)進(jìn)行運(yùn)算的指令,無(wú)論哪種算數(shù)指令,都使用Java虛擬機(jī)的數(shù)據(jù)類型,由于沒(méi)有直接支持byte、short、char和boolean類型的算術(shù)指令,對(duì)于這類數(shù)據(jù)的運(yùn)算,應(yīng)使用操作int類型的指令代替。整數(shù)和浮點(diǎn)數(shù)的算數(shù)指令在溢出和被除零的時(shí)候也有各自不相同的行為表現(xiàn)。所有的算術(shù)指令如下:

  • 加法指令: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、lshl、fshl、dshl;
  • 按位或指令:ior、lor;
  • 按位與指令:iand、land;
  • 按位異或指令:ixor、lxor;
  • 局部變量自增指令:iinc;
  • 比較指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp。
類型轉(zhuǎn)換指令

類型轉(zhuǎn)換指令可以將兩種不同的數(shù)值類型的進(jìn)行相互轉(zhuǎn)換,這些轉(zhuǎn)換操作一般用于實(shí)現(xiàn)用戶代碼中的顯示類型轉(zhuǎn)換操作或者用來(lái)處理字節(jié)碼指令集中數(shù)據(jù)類型相關(guān)指令無(wú)法與數(shù)據(jù)類型一一對(duì)應(yīng)的問(wèn)題。

JVM直接支持(即轉(zhuǎn)換時(shí)無(wú)需顯示的轉(zhuǎn)換指令)以下數(shù)值類型的寬化類型轉(zhuǎn)換(Widening Numberic Conversions,即小范圍類型向大范圍類型的安全轉(zhuǎn)換):
int類型到long、float、或者double類型;
long類型到float、double類型;
float類型到double類型。
相對(duì)的,處理窄化類型轉(zhuǎn)換(Narrowing Numberic Conversions)時(shí) ,必須顯示地使用轉(zhuǎn)換指令來(lái)完成,這些轉(zhuǎn)換指令包括:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l、d2f。窄化類型轉(zhuǎn)換可能導(dǎo)致轉(zhuǎn)換結(jié)果產(chǎn)生不同的正負(fù)號(hào)、不同的數(shù)量級(jí)情況、轉(zhuǎn)換過(guò)程很可能導(dǎo)致數(shù)值的精確度丟失。
注意:數(shù)值類型的窄化處理永遠(yuǎn)不可能導(dǎo)致虛擬機(jī)拋出運(yùn)行時(shí)異常。

對(duì)象創(chuàng)建與訪問(wèn)指令

雖然類實(shí)例和數(shù)組都是對(duì)象,但JVM對(duì)類實(shí)例和數(shù)組的創(chuàng)建與操作采用了不同的字節(jié)碼指令。對(duì)象創(chuàng)建后,就可以通過(guò)對(duì)象訪問(wèn)指令獲取對(duì)象實(shí)例或者數(shù)組中的字段或者數(shù)組元素,這些指令如下:
1,創(chuàng)建類實(shí)例的指令:new;
2,創(chuàng)建數(shù)組的指令:newarray、anewarray、multianewarray;
3,訪問(wèn)類字段(static字段,或者成為類變量)和實(shí)例字段(非static字段,或者稱為實(shí)例變量)的指令:getfield、putfield、getstatic、putstatic;
4,把一個(gè)數(shù)組元素加載到操作數(shù)棧的指令:baload、caload、saload、iaload、laload、faload、daload、aaload;
5,將一個(gè)操作數(shù)棧的值存儲(chǔ)到數(shù)組元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore;
6,取數(shù)組長(zhǎng)度的指令:arraylength;
7,檢查類實(shí)例類型的指令:instanceof、checkcast。

操作數(shù)棧管理指令

如同操作一個(gè)普通數(shù)據(jù)結(jié)構(gòu)中的堆棧那樣,JVM直接提供了一些用于直接操作操作數(shù)棧的指令,包括:
1,將操作數(shù)棧的棧頂一個(gè)或兩個(gè)元素出棧:pop、pop2;
2,復(fù)制棧頂一個(gè)或兩個(gè)數(shù)值并將復(fù)制值或雙份的復(fù)制值重新壓入棧頂:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2;
3,將棧最頂端的兩個(gè)數(shù)值互換:swap。

控制轉(zhuǎn)移指令

控制轉(zhuǎn)移指令可以讓JVM有條件或者無(wú)條件地從指定的位置指令而不是控制轉(zhuǎn)移指令的下一條指令繼續(xù)執(zhí)行程序,從概念模型上理解,可以認(rèn)為控制轉(zhuǎn)移指令就是在有條件或者無(wú)條件地修改PC寄存器的值。控制轉(zhuǎn)移指令如下:
條件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq和if_acmpne;
復(fù)合條件分支:tableswitch、lookupswitch;
無(wú)條件分支:goto、goto_w、jsr、jsr_w、ret;

在JVM中有專門(mén)的指令集用來(lái)處理int(boolean型、byte型、char型和short型的條件分支比較也都用int型,對(duì)于float型、double型的條件分支比較操作則會(huì)先執(zhí)行相應(yīng)類型的比較運(yùn)算指令,它會(huì)返回一個(gè)整型值到操作數(shù)棧中,然后按int型執(zhí)行)和reference類型的條件分支比較操作,為了可以無(wú)需明顯標(biāo)識(shí)一個(gè)實(shí)體值是否為null,也有專門(mén)的指令來(lái)檢測(cè)null值。

方法調(diào)用和返回指令

方法調(diào)用指令舉例:
invokevirtual指令用于調(diào)用對(duì)象的實(shí)例方法,根據(jù)對(duì)象的實(shí)際類型進(jìn)行分派(虛方法分派),這也是java語(yǔ)言最常見(jiàn)的方法分派方式;
invokeinterface指令用于調(diào)用接口方法,它會(huì)在運(yùn)行時(shí)搜索一個(gè)實(shí)現(xiàn)了這個(gè)接口方法的對(duì)象,找出適合的方法進(jìn)行調(diào)用;
invokespecial指令用于調(diào)用一些需要特殊處理的實(shí)例方法,包括實(shí)例初始化方法,私有方法和父類方法;
invokestatic指令用于調(diào)用類方法(static方法);
invokedynamic指令用于在運(yùn)行時(shí)動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法并執(zhí)行該方法,前面4條調(diào)用指令的分派邏輯都固話在JVM內(nèi)部,而invokedynamic指令的分派邏輯是由用戶所設(shè)定的引導(dǎo)方法決定的。
方法調(diào)用指令與數(shù)據(jù)類型無(wú)關(guān),而方法返回指令是根據(jù)返回值的類型區(qū)分的,包括ireturn(當(dāng)返回值是boolean、byte、char、short和int類型使用)、lreturn、freturn、dreturn和areturn,另外還有一條return指令供聲明為void的方法、實(shí)例初始化方法以及類和接口的類初始化方法使用。

異常處理指令

在Java程序中顯示拋出異常的操作(throw語(yǔ)句)都有athrow指令來(lái)實(shí)現(xiàn),除了用throw語(yǔ)句顯示拋出異常的情況之外,JVM規(guī)范還規(guī)定了許多運(yùn)行時(shí)異常會(huì)在其他Java虛擬機(jī)指令檢測(cè)到異常時(shí)自動(dòng)拋出。而在Java虛擬機(jī)之中,處理異常不是由字節(jié)碼指令來(lái)實(shí)現(xiàn)的,而是采用異常表來(lái)完成的。

同步指令

JVM可以支持方法級(jí)的同步和方法內(nèi)部一段指令序列的同步,這兩種同步都使用管程(Monitor)來(lái)支持的。
五:公有設(shè)計(jì)和私有實(shí)現(xiàn)

JVM規(guī)范描繪了JVM應(yīng)有的共同程序存儲(chǔ)格式:Class文件格式以及字節(jié)碼指令集。
理解公有設(shè)計(jì)和私有實(shí)現(xiàn)之間的分界線是很有必要的,JVM實(shí)現(xiàn)必須能夠讀取Class文件并精確實(shí)現(xiàn)包含在其中的JVM代碼的語(yǔ)義。按照虛擬機(jī)規(guī)范一成不變地逐字實(shí)現(xiàn)其中的要求內(nèi)容是一種途徑;但是也可以在滿足JVM規(guī)范要求的情況下對(duì)具體實(shí)現(xiàn)做出修改和優(yōu)化也是可以的,并且JVM規(guī)范明確鼓勵(lì)這么做。
虛擬機(jī)實(shí)現(xiàn)者可以使用這種伸縮性來(lái)讓虛擬機(jī)實(shí)現(xiàn)更高的性能、更低的內(nèi)存消耗或者更好的可移植性,選擇哪種特性取決于JVM實(shí)現(xiàn)的目標(biāo)和關(guān)注點(diǎn)。虛擬機(jī)實(shí)現(xiàn)主要有以下兩種方式:

  • 將輸入的虛擬機(jī)代碼在加載或執(zhí)行時(shí)翻譯成另外一種虛擬機(jī)的指令集;
  • 將輸入的虛擬機(jī)代碼在加載或執(zhí)行時(shí)翻譯成宿主機(jī)CPU的本地指令集。

精確定義的虛擬機(jī)和目標(biāo)文件格式不應(yīng)當(dāng)對(duì)虛擬機(jī)實(shí)現(xiàn)者創(chuàng)造性產(chǎn)生太多的限制,Java虛擬機(jī)應(yīng)被設(shè)計(jì)成可以允許有眾多不同的實(shí)現(xiàn),并且各種實(shí)現(xiàn)可以在保持兼容性的同時(shí)提供不同的、新的、有趣的解決方案。

六:Class文件結(jié)構(gòu)的發(fā)展
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容