JAVA字節碼學習

學習資料來自java

深刻理解JVM虛擬機segmentfault

http://www.javashuo.com/article/p-msqwsymi-a.html數組

https://blog.csdn.net/luanlouis/article/details/50412126架構

http://www.javashuo.com/article/p-msqxjymb-eh.htmlide

Java虛擬機的指令由一個字節長度的,表明着某種特定操做含義的數字(簡稱操做碼,Opcode)以及跟隨其後的零至多個表明此操做所需參數(稱做操做數,Operands)來構成的。Java虛擬機採用面向操做數棧而不是寄存器(Android的Dalvik虛擬機則是依靠寄存器)架構的,因此大多數的指令都不包含操做數,只有一個操做碼。post

因爲Java虛擬機操做碼的長度限制在了一個字節(0-255),意味着指令集的操做碼總數不可能超過256條。學習

不考慮異常處理的話,Java虛擬機的解釋器可使用下面的僞代碼當作最基本的執行模型來理解:ui

do{
	自動計算PC寄存器的值+1;
	根據PC寄存器的指示位置,從字節碼流中取出操做碼;
	if(字節碼存在操做數)
	{
		從字節流中取出操做數;
	}
	執行操做碼所定義的操做;
}while(字節碼長度>0)

字節碼與數據類型

在Java虛擬機的指令集中,大多數的指令都包含了其操做所對應的數據類型信息。如iload,fload指令用於從局部變量表中加載int,float型數據到操做數棧中。lua

對於大部分與數據類型相關的字節碼指令,它們的操做碼助記符中都有特殊的字符來代表專門爲哪一種數據類型服務:i表明對int類型的數據操做,l表明long,s表明short,b表明byte,c表明char,f表明float,d表明double,a表明reference。 也有一些指令的助記符中沒有明確地指明操做類型的字母,如arraylength指令,它沒有表明數據類型的特殊字符,但操做數永遠只能是一個數組類型的對象。 還有另一些指令,如無條件跳轉指令goto則是與數據類型無關的。.net

Java虛擬機指令所支持的數據類型以下:

加載和存儲指令

加載和存儲指令用於將數據在棧幀中的局部變量表和操做數棧之間來回傳輸,指令內容以下:

  • 將一個局部變量加載到操做棧:iload,iload_<n>,lload,lload_<n>,fload,fload_<n>,dload,dload_<n>,aload,aload_<n>。
  • 將一個數值從操做棧存儲到局部變量表:istore,istore_<n>,lsotre,lsotre_<n>,fsotre,fsotre_<n>,dsotre,dsotre_<n>,asotre,asotre_<n>。
  • 加載一個常量到操做數棧:bipush.sipush,ldc,ldc_w,ldc2_w,aconst_null,iconst_ml,iconst_<i>,lconst_<l>,fconst_<f>,dconst_<d>。
  • 擴充局部變量表的訪問索引指令:wide。

記錄常量指令的區別:

當int取值-1~5時,JVM採用iconst指令將常量壓入棧中。

當int取值-128~127時,JVM採用bipush指令將常量壓入棧中。

當int取值-32768~32767時,JVM採用sipush指令將常量壓入棧中。

當int取值-2147483648~2147483647時,JVM採用ldc指令將常量壓入棧中。

存儲數據的操做數棧和局部變量表主要就是由加載和存儲指令進行操做,除此以外,還有少許指令,如訪問對象的字段或者數組元素的指令也會向操做數棧傳輸數據。

上述的_<n>表述一組數據,如iload_<n>,它表明了 iload_0、iload_一、iload_2 和 iload_3 這幾條指令。iload_0的語義與操做數棧數爲0時的iload指令語義徹底一致。

運算指令

運算指令用於對兩個操做數棧上的值進行某種特定特定運算,並把結果從新存儲到操做棧頂。對於運算指令而言能夠分紅兩種:** 對整形數據進行運算的指令與對浮點型數據進行運算的指令。 **不管哪一種運算指令,都是用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。

類型轉換指令

類型轉換指令可讓兩種不一樣的數值類型進行互相轉換。Java虛擬機直接支持如下數值類型的寬化類型轉換(即小範圍到大範圍的轉換):

  • int類型轉long,float或者double類型
  • long類型到float,double類型
  • float類型到double類型

對於窄話類型轉換,須要顯示使用轉換指令完成,指令包括:i2b,i2c,i2s,l2i,f2i,f2l,d2i,d2l,d2f。對於窄化類型轉換結果會致使轉換結果產生不一樣的正負號,精度丟失的狀況,好比float轉int。

對象建立和訪問指令

雖然類實例和數組都是對象,可是Java虛擬機對於這兩種的建立和操做使用了不一樣的字節碼指令,對象建立後,就能夠經過對象訪問指令獲取對象實例或者數組實例中的字段或者數組元素。對應指令以下:

  • 建立類實例的指令:new。
  • 建立數組的指令:newarray,anewarray,multianewarray。
  • 訪問類變量和實例變量的指令:getfield,putfield,getstatic,putstatic。
  • 把一個數組元素加載到操做數棧的指令:baload,caload,saload,iaload,laload,faload,daload,aaload。
  • 把一個操做數棧的值存儲到數組元素中的指令:bstore,castore,sastore,iastore,fastore,dastore,aastore。
  • 取數組長度的指令:arraylength。
  • 檢查類實例類型的指令:instanceof,checkcast。

操做數棧管理指令

Java虛擬機提供了一下直接操做操做數棧的指令,包括:

  • 將操做數棧的棧頂一個或者兩個元素出棧:pop,pop2。
  • 複製棧頂一個或者兩個數值並將複製值或者雙份的複製值從新壓入棧頂:dup,dup2,dup_x1,dup2_x1,dup_x2,dup2_x2。
  • 將棧最頂端的兩個數值互換:swap。

控制轉移指令

控制轉移指令就是在有條件或者無條件修改pc寄存器的值,即代碼中的if,else,switch等關鍵字,指令以下:

  • 條件分支:ifeq、ifne、iflt、ifle、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmple、if_icmpgt、if_icmpge、if_acmpeq和if_acmpne。
  • 複合條件分支:tableswitch、lookupswitch。
  • 無條件分支:goto、goto_w、jsr、jsr_w、ret。

方法調用和返回指令

  • invokevirtual指令:調用對象的實例方法,根據對象的實際類型進行分派(虛方法分派)。
  • invokeinterface指令:調用接口方法,在運行時搜索一個實現這個接口方法的對象,找出合適的方法進行調用。
  • invokespecial:調用須要特殊處理的實例方法,包括實例初始化方法,私有方法和父類方法
  • invokestatic:調用類方法(static)
  • invokedynamic:用於運行時動態解析出調用點限定符所引用的方法,並執行該方法。

方法返回指令是根據返回值的類型區分的,包括ireturn(返回值是boolean,byte,char,short和 int),lreturn,freturn,drturn和areturn,另一個return供void方法,實例初始化方法,類和接口的類初始化i方法使用。

異常處理指令

在Java程序中顯式拋出異常的操做(throw語句)都有athrow 指令來實現,除了用throw 語句顯示拋出異常狀況外,Java虛擬機規範還規定了許多運行時異常會在其餘Java虛擬機指令檢測到異常情況時自動拋出。 在Java虛擬機中,處理異常不是由字節碼指令來實現的,而是採用異常表來完成的。

同步指令

方法級的同步是隱式的,無需經過字節碼指令來控制,它實如今方法調用和返回操做中。虛擬機從方法常量池中的方法標結構中的 ACC_SYNCHRONIZED標誌區分是不是同步方法。方法調用時,調用指令會檢查該標誌是否被設置,若設置,執行線程先成功持有管程,而後才能執行方法,最後方法完成釋放管程。

若是在同步方法執行期間,方法拋了異常,而且在內部沒法處理異常,這個同步方法所持有的管程將會在異常拋到外部時自動釋放。

同步一段指令集序列,一般由synchronized塊標示,JVM指令集中有monitorenter和monitorexit來支持synchronized語義。 結構化鎖定是指方法調用期間每個monitor退出都與前面monitor進入相匹配的情形。


總結:

這裏只是學習了一下理論的知識,弄明白了對應指令作的對應的事情,經過javap反編譯出來的文件中就包含了對應的指令:

public int testInt(java.lang.Object);
   descriptor: (Ljava/lang/Object;)I
   flags: ACC_PUBLIC
   Code:
     stack=2, locals=7, args_size=2
        0: aload_1
        1: dup
        2: astore_2
        3: monitorenter
        4: bipush        11
        6: istore_3
        7: bipush        22
        9: istore        4
       11: aload_1
       12: invokevirtual #12                 // Method java/lang/Object.hashCod
:()I
       15: iconst_2
       16: irem
       17: ifne          27
       20: bipush        33
       22: istore_3
       23: bipush        44
       25: istore        4
       27: iload_3
       28: iload         4
       30: iadd
       31: istore        5
       33: iload         5
       35: aload_2
       36: monitorexit
       37: ireturn
       38: astore_3
       39: iconst_0
       40: aload_2
       41: monitorexit
       42: ireturn
       43: astore        6
       45: aload_2
       46: monitorexit
       47: aload         6
       49: athrow

在以後的學習中,須要應用的對應的分析當中,明白上述的執行過程。

相關文章
相關標籤/搜索