Java位元組碼
Java 位元組碼(英語:Java bytecode)是Java虛擬機器執行的一種指令格式。大多數操作碼都是一個位元組長,而有些操作需要參數,導致了有一些多位元組的操作碼。而且並不是所有可能的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)
- GCJ,GNU Compiler for Java,編譯Java代碼到Java位元組碼;亦可以編譯到機械碼。作為GNU Compiler Collection (GCC)的一部分提供。
有一些專案提供Java組譯器以便於直接用Java位元組碼進行開發。主要的Java組譯器如下:
- Jasmin,讀取Java類別的文字描述;用一種簡單的使用Java虛擬機器指令的類組譯語法,輸出Java類別檔案 [3]
- Jamaica, 一種為Java虛擬機器編寫的宏匯編語言。其中,類與介面由Java語法定義,而其中的方法卻由Java位元組碼定義。[4]
還有其他的一些編譯器,對於其他語言生成Java位元組碼,使其可以執行在Java虛擬機器之上。
- ColdFusion
- JRuby和Jython, 兩種基於Ruby和Python的手稿語言
- Groovy, 一種基於Java的手稿語言
- Scala,一種類型安全的通用程式語言,支援物件導向程式設計和函數式程式設計
- JGNAT和AppleMagic,編譯Ada語言到Java位元組碼
- Clojure, 一種函數式的通用程式語言,提供優秀的並行性。是一種LISP方言
- MIDletPascal
- JavaFX Script 由太陽微系統公司開發的一種手稿語言,執行於Java虛擬機器之上
執行
編輯當前已經有很多種Java虛擬機器產品,包括了自由軟件和商業軟件。 如果在Java虛擬機器之中執行Java位元組碼並不理想,則可以使用一些工具例如GNU Compiler for Java將Java代碼或Java位元組碼編譯成機械碼並由硬件直接執行。 而有一些處理器可以直接執行Java位元組碼,這種處理器名為Java處理器。
對動態語言的支援
編輯Java虛擬機器對動態型別語言提供了一定的支援。但絕大多數的Java虛擬機器指令集是基於靜態型別語言的。在靜態型別機制下,方法呼叫中的類型分析都是在編譯時執行的,而且缺乏一種機制在執行時確定一個類型已經確定相應的方法。
JSR292[5]中,在Java虛擬機器層次增加了一種支援動態型別的指令invokedynamic
,以支援在動態型別檢測中的方法呼叫。 達文西機器則是一種支援這種動態型別呼叫的虛擬機器。 而所有支援JSE 7的Java虛擬機器都應支援invokedynamic操作碼。
參考文獻
編輯- ^ Understanding bytecode makes you a better programmer. [2014-01-28]. (原始內容存檔於2008-12-08).
- ^ 存档副本. [2014-01-28]. (原始內容存檔於2013-02-21).
- ^ Jasmin Home Page. [2020-06-21]. (原始內容存檔於2020-04-17).
- ^ Jamaica: The Java Virtual Machine (JVM) Macro Assembler. [2014-01-28]. (原始內容存檔於2012-06-24).
- ^ see JSR 292. [2014-01-28]. (原始內容存檔於2020-12-20).