Java之字節碼(3) - 簡單介紹


 轉載來自

首先了解一下理論知識:

字節碼:

Class文件是8位字節流,按字節對齊。之所以稱為字節碼,是因為每條指令都只占據一個字節,所有的操作碼和操作數都是按字節對齊的。如:0×03表示iconst_0

Class文件的頭4個字節稱為魔數(Magic Number),它的唯一作用是用於確認該文件是否是能被JVM接受的Class文件。魔數值為:0xCAFEBABE。

緊接着魔數的4個字節是Class文件的版本號:第5和第6字節是次版本號(Minor Version),第7和第8字節是主版本號(Major Version)。Java的版本號從45開始的,JDK6的版本號是50。

javap –verbose class文件,查看字節碼內容

全限定名:把類全名中的“.”替換成“/”最后加入一個“;”表示結束。如com/test/TestClass;

描述符:基本類型及void用大寫字符表示,對象類型用字符L加對象的全限定名表示。

標識字符 含義
B 基本類型byte
C char
D double
F float
I int
J long
S short
Z boolean
V void
L 對象類型,如Ljava/lang/Object;

 

對於數組類型,每一維度將使用一個前置的“[”字符來描述,如定義一個“java.lang.String[][]”類型的二維數組,將被記錄為:“[[Ljava/lang/String;”,一個整型數組“int[]”將被記錄為“[I”.

用描述符來描述方法時,按照先參數列表,后返回值的順序描述,參數列表按照參數的嚴格順序放在一組小括號“( )”之內,如方法void inc()的描述符為“( )V”,方法java.lang.String toString() 的描述符合為“( )Ljava/lang/String;”,方法int indexOf(char[] source, int sourceOffset,int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)的描述符為“([CII[CIII)I”

類構造器“<clinit>”方法,實例構造器“<init>”

JVM中最基本的數據單元是字,字長必須足夠大,至少一個字長足以持有byte、short、int等的值,2個字長足以持有long、double的值,字長可以選擇32位或者64位。

字長是CPU的主要技術指標之一,指的是CPU一次能並行處理的二進制位數,字長總是8的整數倍,通常PC機的字長為16位(早期),32位,64位。

棧幀的2個部分:局部變量區和操作數棧,是按字來定義的。當把值放入局部變量區或者操作數棧時,它將占有1個或2個字單元。

每啟動一個新線程,JVM都為它分配一個Java棧,Java棧以幀為單位保存線程的運行狀態,JVM對Java棧執行2種操作:以幀為單位的壓棧和出棧。

每當線程調用一個Java方法時,JVM會在線程的Java棧中壓入一個新幀,而這個新幀也成了當前幀,當執行這個方法時,它使用這個幀來存儲參數、局部變量、中間運算結果等數據。

Java方法有2種方式完成,一種通過return返回,一種通過拋出異常中止,不管那種方式,JVM都將當前幀彈出Java棧然后釋放掉,這樣上一個方法的幀就成為當前幀了。

Java棧上的所有數據都是此線程私有的,任何線程都不能訪問另一個線程的棧數據,因此棧數據是線程安全的。

棧幀由3部分組成:局部變量區、操作數棧、幀數據區。

局部變量區和操作數棧是以字長(32位)為單位的數組。

局部變量區包含方法的參數和局部變量,編譯器首先按把這些參數放入局部變量數組。Java棧幀的局部變量區被組織為一個以字長為單位,從0開始計數 的數組。字節碼指令通過從0開始的索引來使用其中的數據,如iload_1(把局部變量區的第2個變量壓入棧頂),byte、short、int等的值在 數組中只占據1項,而long、double的值在數組中占據連續的2項。

操作數棧:和局部變量區一樣,操作數棧也是被組織成一個以字長為單位的數組,但是它不是通過索引來訪問,而是通過標志的棧操作:壓棧和出棧來訪問的。如iadd指令,從操作數棧中彈出2個整數,執行加法運算,然后將其結果壓回操作數棧。

iload_0 //將局部變量區的第1個變量壓入棧

iload_1 //將局部變量區的第2個變量壓入棧

iadd   //棧中彈出2個整數,執行加法運算,然后將其結果壓回操作數棧

istore_2 //將棧頂的整數出棧,並存入局部變量區的第3個變量

一般讀取局部變量區的數據,需要把局部變量區的變量壓入棧,

把值寫到局部變量區,也需要先壓入棧,再寫到局部變量區

幀數據區:存放常量池(要訪問的類、字段、方法名等),異常表等數據。

LineNumberTable:字節碼偏移量與源代碼之間的映射關系。

常見指令:iload_1, istore_1, iconst_1, ldc, bipush, pop, dup, iadd, isub, imul, idiv, return, goto,invoke…, new, newarray, arraylength, instanceof,athrow,monitorenter, monitorexit

然后用字節碼了解一下JVM的語法糖:

語法糖:

泛型、自動裝箱、自動拆箱、循環遍歷、變長參數、條件編譯、內部類、枚舉類、斷言語句、對枚舉的switch

類型擦除:

public class TestCls3

{//編譯失敗,因為List<String>List<Integer>的泛型被擦除,變成原生類型List

    public static void method(List<String> list)

    {

        System.out.println("invoke method1");

    }

   

    public static void method(List<Integer> list)

    {

        System.out.println("invoke method2");

}

}

public class TestCls3

{//可以執行,因為在Class文件中,只有描述符不完全一致的兩個方法就可以共存

//也就是說兩個方法如果有相同的名稱和特征簽名,但返回值不同,也是可以共存在一個Class文件的

    public static String method(List<String> list)

    {

        System.out.println("invoke method1");

        return "";

    }

   

    public static int method(List<Integer> list)

    {

        System.out.println("invoke method2");

        return 1;

    }

   

    public static void main(String[] args)

    {

        method(new ArrayList<String>());

        method(new ArrayList<Integer>());

    }

}

javap –verbose demo.TestCls3

Constant pool:

const #17 = Asciz       (Ljava/util/List<Ljava/lang/String;>;)Ljava/lang/String;

;

const #39 = Asciz       (Ljava/util/List<Ljava/lang/Integer;>;)I;

public static java.lang.String method(java.util.List);  //方法名擦除為List

  Signature: length = 0×2

   00 11   //指到常量池中的第17

public static int method(java.util.List);     //方法名擦除為List

  Signature: length = 0×2

   00 27   //指到常量池中的第39

類型擦除,僅僅對方法的Code屬性中的字節碼進行擦除,元數據Signature還是保留了泛型數據。(Method類的signature變量)

Java的條件編譯

只有條件為常量且只有if語句才能有這種效果

public class TestCls5

{

    public static void main(String[] args)

    {

        if (true)

        {

            System.out.println(“true”);

        }

        else

        {

            System.out.println(“false”);

        }

    }

}

編譯后的字節碼只包含:System.out.println(“true”);

public class TestCls5

{

  public static void main(String[] args)

  {

    System.out.println(“true”);

  }

}

自增++操作的線程非安全:

public class TestCls5

{

    private static volatile int count;

    public static void main(String[] args)

    {

        count++;

    }

}

對應字節碼(分為4個指令,在多線程下訪問可能出現臟數據):

Code:

 0:   getstatic       #18; //Field count:I   獲取指定類的靜態域,並將其值壓入棧頂

 3:   iconst_1                         將整型常量1壓入棧頂

 4:   iadd                             將棧頂的2個值出棧並相加,然后將結果入棧頂

 5:   putstatic       #18; //Field count:I   為指定的類的靜態域賦值

 8:   return                            方法返回

字符串的+操作(javac編譯器會對String連接做自動優化):

public String constractStr(String str1, String str2, String str3)

    {

        return str1 + str2 + str3;

}

對應字節碼(JDK1.5之后轉換為調用StringBuilder.append方法):

  Code:

   0:   new     #24; //class java/lang/StringBuilder

   3:   dup

   4:   aload_1

   5:   invokestatic    #26; //Method java/lang/String.valueOf:(Ljava/lang/Objec

t;)Ljava/lang/String;

   8:   invokespecial   #32; //Method java/lang/StringBuilder.”<init>”:(Ljava/la

ng/String;)V

   11:  aload_2

   12:  invokevirtual   #35; //Method java/lang/StringBuilder.append:(Ljava/lang

/String;)Ljava/lang/StringBuilder;

   15:  aload_3

   16:  invokevirtual   #35; //Method java/lang/StringBuilder.append:(Ljava/lang

/String;)Ljava/lang/StringBuilder;  ――調用StringBuilder的append方法

   19:  invokevirtual   #39; //Method java/lang/StringBuilder.toString:()Ljava/l

ang/String;

   22:  areturn     ――返回引用

public String constractStr()

    {

        return “str1″ + “str2″ + “str3″;

}

對應的字節碼:

Code:

 0:   ldc     #24; //String str1str2str3         –將字符串常量壓入棧頂

 2:   areturn

public String constractStr(String str3)

    {

        return “str1″ + “str2″ + str3;

}

對應的字節碼:

  Code:

   0:   new     #24; //class java/lang/StringBuilder

   3:   dup

   4:   ldc     #26; //String str1str2   –將字符串常量str1str2壓入棧頂

   6:   invokespecial   #28; //Method java/lang/StringBuilder.”<init>”:(Ljava/la

ng/String;)V

   9:   aload_1

   10:  invokevirtual   #31; //Method java/lang/StringBuilder.append:(Ljava/lang

/String;)Ljava/lang/StringBuilder;     ――調用StringBuilder的append方法

   13:  invokevirtual   #35; //Method java/lang/StringBuilder.toString:()Ljava/l

ang/String;

   16:  areturn

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM