2010-04-14 45 views
12

我一直在(Java)Bytecode上工作了一段時間,但是,我從來沒有想過要問爲什麼鍵入了一些指令?我知道在ADD操作中,我們需要區分整數加法和FP加法(這就是爲什麼我們有IADD和FADD)。但是,爲什麼我們需要區分ISTORE和FSTORE?它們都涉及完全相同的操作,它將32位從棧中移動到局部變量位置?類型字節碼

我能想到的唯一答案是爲了防止這種類型安全:(ILOAD,ILOAD,FADD)。但是,我相信類型安全已經在Java語言級別實施。好的,Class文件格式並不直接與Java結合,那麼這是爲不支持它的語言實施類型安全性的一種方式嗎?任何想法?謝謝。

編輯:跟進裏迪的答案。我寫了這個最小的方案:

public static void main(String args[]) 
{ 
    int x = 1; 
} 

其編譯爲:使用一個字節碼編輯器

iconst_1 
istore_1 
return 

,我改變了第二個指令:

iconst_1 
fstore_1 
return 

,並返回一個的java.lang .VerifyError:期望在堆棧上找到浮動。

我想知道,如果在堆棧中沒有關於類型的信息,只是位,FSTORE指令是如何知道它是在處理int而不是float?

注意:我找不到這個問題更好的標題。隨意改進它。

回答

17

這些說明是鍵入以確保程序是類型安全的。在加載類時,虛擬機會對字節碼執行驗證,以確保例如float不會作爲期望整數的方法的參數傳遞。這種靜態驗證要求驗證者可以爲任何給定的執行路徑確定棧上值的類型和數量。加載和存儲指令需要類型標記,因爲堆棧幀中的局部變量沒有輸入(即,您可以存放到局部變量,然後fstore到相同的位置)。指令中的類型標記允許驗證者知道每個局部變量中存儲了什麼類型的值。

驗證程序查看方法中的每個操作碼,並在執行每個操作碼後跟蹤堆棧和局部變量中的類型。你是對的,這是另一種形式的類型檢查,並且複製了一些由java編譯器完成的檢查。驗證步驟可防止加載任何可能導致VM執行非法指令的代碼,並確保Java平臺的安全屬性,而不會在每次操作之前造成檢查類型的大運行時間損失。運行時類型檢查每個操作碼在每次執行該方法時都會影響性能,但是在加載該類時只進行一次靜態驗證。

案例1:

Instruction    Verification Stack Types   Local Variable Types 
----------------------- --------------- ---------------------- ----------------------- 
<method entry>   OK    []      1: none 
iconst_1    OK    [int]     1: none 
istore_1    OK    []      1: int 
return     OK    []      1: int 

案例2:

Instruction    Verification Stack Types   Local Variable Types 
----------------------- --------------- ---------------------- ----------------------- 
<method entry>   OK    []      1: none 
iconst_1    OK    [int]     1: none 
fstore_1    Error: Expecting to find float on stack 

給出錯誤的原因是驗證知道fstore_1需要一個浮在堆棧上,但執行前面的指令的結果留下一個int在堆棧上。

該驗證在不執行操作碼的情況下完成,而是通過查看指令的類型來完成,就像編寫(Integer)"abcd"時java編譯器給出錯誤一樣。編譯器不必運行該程序即可知道"abcd"是一個字符串,不能轉換爲Integer

+0

感謝您的回答。因此驗證器在執行類來檢測這種類型的錯誤之前執行一些數據流分析。有趣的學習:) – 2010-04-15 07:38:24

3

用我最好的猜測回答你的第一個問題:這些字節碼是不同的,因爲它們可能需要不同的實現。例如,一個特定的體系結構可能在主棧上保留整數操作數,但在硬件寄存器中保留浮點操作數。

要回答你的第二個問題,VerifyError在類加載時拋出,而不是在執行時拋出。驗證過程描述爲here;注意通過#3。

+0

+1的鏈接。謝謝你提醒我VerifyError在加載時拋出,而不是在運行時拋出。 – 2010-04-15 07:34:40

4

Geoff Reedy在他的回答中解釋了驗證者在加載類時的功能。我只想補充一點,你可以使用JVM參數禁用驗證器。這不推薦!

對於示例程序(與ICONST和FSTORE),禁用與驗證運行的結果是一個VM的錯誤與下面的消息將暫停JVM:

=============== DEBUG MESSAGE: illegal bytecode sequence - method not verified ================ 

# 
# An unexpected error has been detected by HotSpot Virtual Machine: 
# 
# EXCEPTION_PRIV_INSTRUCTION (0xc0000096) at pc=0x00a82571, pid=2496, tid=3408 
# 
# Java VM: Java HotSpot(TM) Client VM (1.5.0_15-b04 mixed mode, sharing) 
# Problematic frame: 
# j BytecodeMismatch.main([Ljava/lang/String;)V+0 
# 
... 
2

所有字節碼必須是一個可證明的類型安全如上所述的靜態數據流分析。然而,這並不能真正解釋爲什麼像_store這樣的指令有不同的類型,因爲類型可以從堆棧中值的類型推斷出來。事實上,有一些像pop,dup和swap這樣的指令可以完成這些操作,並且可以在多種類型上運行。爲什麼某些指令是鍵入的,而其他的則不是那些只能由Java的原始開發人員解釋的東西。