首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

JVM 驗證階段不檢查介面實作狀況

     編輯:Java 相關知識

背景

繼續探討執行 Java 程式所需各種檢查發生的時機,這次關注介面呼叫問題。

JVM 方法呼叫指令差異

當前 JVM 規範中,與方法呼叫相關的指令有四個:invokevirtual、invokeinterface、invokestatic、invokespecial。呼叫介面方法時使用 invokeinterface,它與其他三指令有顯著差異:不要求 JVM 驗證器檢查被呼叫物件的型別;其他三指令都要求驗證被呼叫物件。意即,使用 invokeinterface 時若被呼叫物件未實作指定介面,應在執行期而非鏈結期拋出異常;其他三指令要求在鏈結期拋出異常。

JVM 規範原文

Java Virtual Machine Specification, 2nd Edition 寫道:

invokeinterface ...
Runtime Exceptions ...
if the class of objectref does not implement the resolved interface, invokeinterface throws an IncompatibleClassChangeError.

留意其他三指令中的 IncompatibleClassChangeError 皆為 Linking Exception 而非 Runtime Exception。

對 Java 程式可見行為

若方法通不過驗證,整個方法不會被執行;若通過驗證但拋出執行期異常,則拋出異常前的程式碼都會被執行。

直接用 Java 語言寫出的程式很難引發此錯誤,因 Java 編譯器會檢查保證型別安全。但 Java class 檔案或位元組碼可由 Java 編譯器外的其他方式產生,此時無編譯器型別安全保證,需依賴 JVM 對位元組碼驗證及執行期檢查。

實際範例演示

先建立介面 IFoo、實作類別 FooImpl、未實作介面的類別 Bar:

public interface IFoo { void method(); }
public class FooImpl implements IFoo { public void method() { System.out.println("FooImpl.method()"); } }
public class Bar { public void anotherMethod() { System.out.println("Bar.anotherMethod()"); } }

構造引發執行期異常程式:

public class TestInterfaceCall { 
    public static void main(String[] args) { 
        IFoo f = new FooImpl(); 
        f.method(); 
        Bar b = new Bar(); 
        ((IFoo)b).method(); // << watch this 
    } 
}

若直接編譯,位元組碼會有 checkcast 指令將 Bar 轉為 IFoo,執行期在該指令報錯。但我們要引發的是介面呼叫相關錯誤:以 b 為被呼叫物件,呼叫 IFoo.method() 而非 Bar 既有方法。需自行產生位元組碼避開 checkcast。

使用 Charles Nutter 的 bitescript 函式庫(需 JRuby 1.2.0+,此處用 1.3.0RC2):

gem install bitescript

產生位元組碼腳本 (test.rb)

require 'rubygems'
require 'bitescript'
include BiteScript
IFoo = Java::IFoo
FooImpl = Java::FooImpl
Bar = Java::Bar
fb = FileBuilder.build(__FILE__) do
  public_class 'TestInterfaceCall' do
    public_static_method 'main', void, string[] do
      # IFoo f = new FooImpl();
      new FooImpl
      dup
      invokespecial FooImpl, '', [void]
      astore 1
      # f.method();
      aload 1
      invokeinterface IFoo, 'method', [void]
      # Bar b = new Bar();
      new Bar
      dup
      invokespecial Bar, '', [void]
      astore 2
      # ((IFoo)b).method();
      aload 2
      ## checkcast IFoo  # skip the cast to trigger IncompatibleClassChangeError
      invokeinterface IFoo, 'method', [void]
      returnvoid
    end
  end
end
fb.generate do |filename, class_builder|
  File.open(filename, 'w') do |file|
    file.write(class_builder.generate)
  end
end

執行腳本,將產生的 TestInterfaceCall.class 與 IFoo.class、FooImpl.class、Bar.class 放同目錄。執行 java TestInterfaceCall:

D:\sdk\jruby-1.3.0RC2\test_bitescript>java TestInterfaceCall
FooImpl.method()
Exception in thread "main" java.lang.IncompatibleClassChangeError
    at TestInterfaceCall.main(test.rb)

程式印出 "FooImpl.method()",證明異常在執行期而非鏈結期拋出。

現今用到 Java 位元組碼改寫/動態產生工具已普遍,使用時若不夠小心,此處提到的執行期異常也有機會遇到。

執行環境:java version "1.6.0_11"

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved