Java 字節碼(英語:Java bytecode)是Java虛擬機執行的一種指令格式。大多數操作碼英語Opcode都是一個字節長,而有些操作需要參數,導致了有一些多字節的操作碼。而且並不是所有可能的256個操作碼都被使用;其中有51個操作碼被保留做將來使用。除此之外,原始Java平台開發商,昇陽微系統,額外保留了3個代碼永久不使用。

與Java的關係

編輯

一個Java程序員並不需要理解所有的Java字節碼。但是,就像IBM developerWorks周刊建議的那樣:「理解字節碼以及理解Java編譯器如何生成Java字節碼與學習匯編知識對於C/C++程序員有一樣的意義。」[1]

指令

編輯

指令碼為一個字節,有256個可能的代碼值(28=256),因此一個字節的指令碼最多可能有256種不同的操作。其中,0x00、0xFE、0xCA、0xFF被指定保留。例如0xCA作為一個Java調試器的中斷指令而從未被語言使用。相似地,0xFE和0xFF也未被語言使用[2]

指令可以基本分為以下幾類:

  • 存儲指令 (例如:aload_0,istore)
  • 算術與邏輯指令 (例如: ladd,fcmpl)
  • 類型轉換指令 (例如:i2b,d2i)
  • 對象創建與操作指令 (例如:new,putfield)
  • 堆棧操作指令 (例如:swap,dup2)
  • 控制轉移指令 (例如:ifeq,goto)
  • 方法調用與返回指令 (例如:invokespecial,areturn)

除此之外,還有一些更特殊的指令,作為異常拋出或同步等作用。

大多數的指令有前綴和(或)後綴來表明其操作數的類型。如下表

前/後綴 操作數類型
i 整數
l 長整數
s 短整數
b 字節
c 字符
f 單精度浮點數
d 雙精度浮點數
z 布爾值
a 引用

例如,"iadd"指令將兩個整數相加;而"dadd"指令將兩個double浮點數相加。

"const"、 "load"、 "store"等涉及存儲命令還會使用"_n"後綴,其中 "load"和"store"命令中的n可以為0到3之間的整數;而"const"命令中的n由類型指定。"const"指令把一個指定類型的值放入堆棧。例如"iconst_5"指令將一個整數5放入堆棧;而"dconst_1"將一個雙精度浮點數1放入堆棧。"aconst_null"指令是放入一個null進堆棧。而對於"load" "store"指令中的n,指定了變量表中的存儲位置。"aload_0"指令把在變量0中的對象(通常是"this"對象)放入堆棧,"istore_1"指令把棧頂的一個整數放入變量1。對於更高的變量,後綴將被去除,而這條指令將需要操作數。

計算模型

編輯

Java字節碼的計算模型是面向堆棧結構計算機的。例如,一個x86處理器的匯編代碼如下

mov eax, byte [ebp-4]
mov edx, byte [ebp-8]
add eax, edx
mov ecx, eax

這段代碼將兩個數值相加,並存入另一個地址。相似的反匯編字節碼如下

0 iload_1
1 iload_2
2 iadd
3 istore_3

在這裡,需要相加的兩個操作數被放入堆棧,而相加操作就在棧中進行,其結果也被放入堆棧。存儲指令之後把棧頂的數據放入一個變量地址。在每條指令前面的數字僅僅是表示這條指令到方法開始處的偏移值。這種堆棧結構也可以推廣到面向對象模型上。例如,有一個"getName"方法如下

 Method java.lang.String getName()
 0 aload_0       // "this"对象被存入变量0
 1 getfield #5 <Field java.lang.String name>
                 // 这个指令从栈顶取出一个对象,并从中搜索一个指定的域
                 // 并将相应的数据存入栈顶。
                 // 这个例子中,"name"域对应于该类中的第五个常量。
 
 4 areturn  	 // 返回栈顶的对象作为函数的返回值

例子

編輯

考慮如下Java代碼

  outer:
  for (int i = 2; i < 1000; i++) {
      for (int j = 2; j < i; j++) {
          if (i % j == 0)
              continue outer;
      }
      System.out.println (i);
  }

假設上述代碼位於一個函數中,Java編譯器可能將代碼翻譯成下述的Java字節碼。

  0:   iconst_2
  1:   istore_1
  2:   iload_1
  3:   sipush  1000
  6:   if_icmpge       44
  9:   iconst_2
  10:  istore_2
  11:  iload_2
  12:  iload_1
  13:  if_icmpge       31
  16:  iload_1
  17:  iload_2
  18:  irem
  19:  ifne    25
  22:  goto    38
  25:  iinc    2, 1
  28:  goto    11
  31:  getstatic       #84; //Field java/lang/System.out:Ljava/io/PrintStream;
  34:  iload_1
  35:  invokevirtual   #85; //Method java/io/PrintStream.println:(I)V
  38:  iinc    1, 1
  41:  goto    2
  44:  return

基於Java字節碼的語言

編輯

最常用的基於Java字節碼的語言就是開發出Java字節碼的Java語言。剛開始,只存在一個由昇陽微系統開發的一個編譯器javac。而現在Java字節碼規範已經可以得到,因此,第三方公司亦開發出支持Java字節碼的編譯器。例如:

  • Jikes,編譯Java源代碼到Java字節碼(由IBM開發,用C++實現)
  • Espresso,編譯Java源代碼到Java字節碼(僅支持Java 1.0)
  • GCJGNU Compiler for Java,編譯Java代碼到Java字節碼;亦可以編譯到機器代碼。作為GNU Compiler Collection (GCC)的一部分提供。

有一些項目提供Java匯編器以便於直接用Java字節碼進行開發。主要的Java匯編器如下:

  • Jasmin,讀取Java類的文字描述;用一種簡單的使用Java虛擬機指令的類匯編語法,輸出Java類文件 [3]
  • Jamaica, 一種為Java虛擬機編寫的宏匯編語言。其中,類與接口由Java語法定義,而其中的方法卻由Java字節碼定義。[4]

還有其他的一些編譯器,對於其他語言生成Java字節碼,使其可以運行在Java虛擬機之上。

運行

編輯

當前已經有很多種Java虛擬機產品,包括了自由軟件和商業軟件。 如果在Java虛擬機之中執行Java字節碼並不理想,則可以使用一些工具例如GNU Compiler for Java將Java代碼或Java字節碼編譯成機器碼並由硬件直接運行。 而有一些處理器可以直接運行Java字節碼,這種處理器名為Java處理器。

對動態語言的支持

編輯

Java虛擬機對動態類型語言提供了一定的支持。但絕大多數的Java虛擬機指令集是基於靜態類型語言的。在靜態類型機制下,方法調用中的類型分析都是在編譯時執行的,而且缺乏一種機制在運行時確定一個類型已經確定相應的方法。

JSR292[5]中,在Java虛擬機層次增加了一種支持動態類型的指令invokedynamic,以支持在動態類型檢測中的方法調用。 達芬奇機器英語Da Vinci Machine則是一種支持這種動態類型調用的虛擬機。 而所有支持JSE 7的Java虛擬機都應支持invokedynamic操作碼。

參考文獻

編輯
  1. ^ Understanding bytecode makes you a better programmer. [2014-01-28]. (原始內容存檔於2008-12-08). 
  2. ^ 存档副本. [2014-01-28]. (原始內容存檔於2013-02-21). 
  3. ^ Jasmin Home Page. [2020-06-21]. (原始內容存檔於2020-04-17). 
  4. ^ Jamaica: The Java Virtual Machine (JVM) Macro Assembler. [2014-01-28]. (原始內容存檔於2012-06-24). 
  5. ^ see JSR 292. [2014-01-28]. (原始內容存檔於2020-12-20).