`
RednaxelaFX
  • 浏览: 3038681 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

为什么JVM与CLR都不对接口方法调用做静态校验?

阅读更多
作者:RednaxelaFX
主页:http://rednaxelafx.iteye.com
日期:2009-06-02

系列笔记:
JVM在校验阶段不检查接口的实现状况
CLR上的接口调用也是在运行时检查的

前面两帖我们看到JVM与CLR这两个主流的高级语言虚拟机都对接口方法调用管得很松,不对其被调用对象做静态类型校验。可是为什么呢?

==================================================================

这个问题先放在一边,让我们来看看如果让JVM执行一段字节码,其中调用了不存在的虚方法会怎样。还是用bitescript来生成class文件:
require 'rubygems'
require 'bitescript'
include BiteScript

JObject = java.lang.Object
JString = java.lang.String

fb = FileBuilder.build(__FILE__) do
  public_class 'TestVirtualCall' do
    public_static_method 'main', void, string[] do
      # Object o = new Object();
      new JObject
      dup
      invokespecial JObject, '<init>', [void]
      astore 1
      
      # int i = ((String)o).length();
      aload 1
      ## checkcast JString                   # without this cast, there will be a
      invokevirtual JString, 'length', [int] # VerifyError at verification time
      istore 2
      
      returnvoid
    end
  end
end

fb.generate do |filename, class_builder|
  File.open(filename, 'w') do |file|
    file.write(class_builder.generate)
  end
end

执行这段脚本生成TestVirtualCall.class,执行之,会看到:
Exception in thread "main" java.lang.VerifyError: (class: TestVirtualCall, method: main signature: ([Ljava/lang/String;)V) Incompatible object argument for function call
Could not find the main class: TestVirtualCall.  Program will exit.

也就是说JVM在校验的时候就检查到了错误:要调用的length()是String类上的,而引用是Object类型的,不是String或其派生类,于是被认为校验失败。

JVM与CLR都以支持静态的面向对象类型为主。由于基类上所有实例方法中非私有的方法都自动会被派生类所继承,所以可以保证这些方法在派生类上肯定存在(有没有被覆盖没关系)。持有某类型的引用时,即便实际指向的是这个类的派生类的实例,至少调用该类型上的方法都肯定没问题。所以JVM与CLR对虚方法调用的限制都可以做得比较严格,可以根据引用的类型(而不是实际实例的类型)来做校验。
虽说这种严格性也使得一些本来可以成功的调用在校验时会被拒绝,像这样:
// Object o = (Object)new ArrayList();
new Ljava/util/ArrayList;
dup
invokespecial java/util/ArrayList."<init>":()V
checkcast java/lang/Object // << ensures the JVM think local 1 is of type Object
astore 1

// o.size(); // note that o actually points to an instance of ArrayList
aload 1
invokevirtual java/util/ArrayList.size:()I

上面这段字节码的最后一条指令在校验的时候会被认为不合法,因为静态校验会认为o的类型是o,与ArrayList.size()要求的被调用对象类型不匹配。但我们通过脑内推导可以看出这里的o实际指向的是ArrayList的实例,这个调用本来应该可以成功的。但JVM不买这帐,宁可认定一些貌似可以貌似不行的状况为不合法,以保证VM的正确性的可证明性。

==================================================================

那调用接口方法的状况呢?

JVM与CLR的类型系统中,接口可以被任何类所实现。基类实现的接口派生也必须实现;基类没有实现的接口,派生类照样可以实现。于是,当我们持有某个类型A的引用时,如果该类型自身并没有实现某接口IB,我们无法确定该引用所指向的对象实例是不是肯定没有实现接口IB:或许类型B继承类型A同时实现了接口IB,而我们所持有的类型A的引用指向的正是类型B的实例。
这种情况无法只用静态检查验证其正确性,但又十分有用,不能一刀切都拒绝掉。所以干脆推迟到运行时通过实际的实例上的类型信息来做检查。
静态校验与动态检查相结合,JVM与CLR等支持静态的面向对象类型系统的虚拟机得以保证其上运行的程序的类型安全。

可以看这样的一个例子,其中IFoo与FooImpl取自前一帖的例子:
require 'rubygems'
require 'bitescript'
include BiteScript

IFoo    = Java::IFoo
FooImpl = Java::FooImpl

fb = FileBuilder.build(__FILE__) do
  public_class 'TestVirtualCall2' do
    public_static_method 'main', void, string[] do
      # Object o = new FooImpl();
      new FooImpl
      dup
      invokespecial FooImpl, '<init>', [void]
      checkcast JObject  # << make sure the JVM think local 1 is of type Object
      astore 1

      # o.method();
      aload 1
      invokeinterface IFoo, 'method', [void] # this works!
      returnvoid
    end
  end
end

fb.generate do |filename, class_builder|
  File.open(filename, 'w') do |file|
    file.write(class_builder.generate)
  end
end

这段脚本生成的TestVirtualCall2.class执行正常,打印出"FooImpl.method()"。
与前面调用虚方法的例子对比,可以看到JVM对接口方法调用的静态检查确实松一些。结合前一帖的例子看,这仍然是类型安全的,虽然要推迟到运行时才能检查。

==================================================================

值得注意的是,以上讨论都是以静态类型系统为前提的。如果类的成员能动态的被任意添加删除修改,那接口或基类所定下的契约就不能保证被实现,也就失去了上述讨论的前提。
分享到:
评论
2 楼 RednaxelaFX 2009-06-05  
cescshen 写道
引用
引用基类上所有实例方法中非私有的方法都自动会被派生类所继承

以前好像看到说,基类的私有方法也会被派生类继承,只是不能调用.

这要看你怎么看待“继承”这个概念了。确实,基类的所有实例成员都会被派生类所继承,包括成员域和成员方法;其中只有成员方法与多态有关,而私有方法不参与多态。如果不能被调用、不参与多态,那么它对外界来说就是不可观察到的,至少不会成为“契约”的一部分。(Java序列化的那俩方法……那是例外)

可以举个例子。在CLR当中,每个类型都与一个方法表的实例联系在一起。方法表当中,继承下来的虚方法的项总是位于方法表中固定偏移量的位置上,紧接着是该类型新增加的虚方法,然后才是私有方法和构造器等。派生类的方法表里不包含基类的私有方法或静态方法的项——既然不能调用,何必记录下来呢?这样的设计在调用虚方法总能使用相对方法表起始位置的固定偏移量,间接调用,而在调用静态方法或私有方法时则使用固定地址,直接调用。
1 楼 cescshen 2009-06-05  
引用
基类上所有实例方法中非私有的方法都自动会被派生类所继承

以前好像看到说,基类的私有方法也会被派生类继承,只是不能调用.

相关推荐

    从JVM内存管理的角度谈谈静态方法和静态属性

    静态方法和静态属性是Java类的特性,它们与非静态成员有着显著的不同。静态方法并不与任何特定对象实例关联,而是直接属于类。这意味着调用静态方法时,无需创建对象实例,只需通过类名即可调用。静态方法内不能直接...

    垃圾回收系列(3):CLR与JVM垃圾回收器的比较扫描.pdf

    本文主要探讨了.NET框架中的CLR(Common Language Runtime)与Java平台上的JVM(Java Virtual Machine)垃圾回收器之间的差异。在垃圾回收领域,两者都采用了分代式垃圾回收策略,但具体实现和优化有所不同。 首先...

    JVM 方法调用之静态分派(详解)

    "JVM 方法调用之静态分派详解" 静态分派是JVM 方法调用中的一种机制,根据分派依据的宗量数可分为单分派和多分派。静态分派的典型应用是方法重载,发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行...

    使用C++创建java虚拟机JVM,使用JNI调用java函数.zip

    3. **获取JNIEnv指针**:成功初始化JVM后,我们获得一个`JNIEnv*`指针,它是C++与Java之间进行方法调用的关键。`JNIEnv`提供了很多函数,如`FindClass`, `GetMethodID`, `CallVoidMethod`等,用于操作Java对象和调用...

    JVM调用Lambda的表达式实现方法原理详解.docx

    Lambda表达式本质上是匿名函数,它们的创建和调用过程涉及到类型推断、方法句柄以及`invokedynamic`指令的使用,使得Lambda可以动态地解析并执行相应的函数式接口方法。 总结来说,理解JVM中的这些调用指令和分派...

    jni调用java静态方法

    在Java类中,我们会定义`native`关键字修饰的静态方法,这将提示JVM该方法需要通过JNI来实现。例如: ```java public class JniStaticCallExample { static { System.loadLibrary("jnicallexample"); // 加载本地...

    java方法调用

    方法调用有两种主要方式:在一个类内部的方法调用与不同类之间的方法调用。目前我们先聚焦于一个类内部的方法调用。 ##### 1. 类内部的方法调用 在同一个类内部,方法调用相对简单。方法调用的语法取决于方法是否...

    DELPHI 11调用JAVA 接口

    使用`CreateJavaVM`函数初始化Java虚拟机(JVM),然后通过`FindClass`查找Java接口类,使用`GetMethodID`获取接口方法的ID,最后通过`CallObjectMethod`或`CallNonvirtualObjectMethod`来调用Java接口。 5. **传递...

    RMI远程方法调用RMI远程方法调用

    RMI远程方法调用是Java平台上的一个关键特性,它允许Java对象在不同的JVM之间进行通信,从而实现分布式计算。RMI的核心理念是让开发者能够像调用本地方法一样调用远程对象的方法,简化了分布式系统的设计和实现。 *...

    Java中的Static块与静态方法

    与静态方法不同,静态块不是由程序员显式调用的,而是由JVM在类加载时自动执行。 #### 执行顺序: 1. **类加载时执行**:静态块在类首次被加载到JVM时执行,这是在任何对象实例化之前发生的。 2. **只执行一次**:...

    LINUX C调用JAVA的静态方法和非静态方法(实例方法)小实例

    在Linux环境中,C语言可以通过Java Native Interface (JNI) 来调用Java的静态和非静态方法,实现两者之间的交互。JNI是Java平台标准的一部分,它允许Java代码和其他语言写的代码进行交互,使得C/C++程序员可以编写...

    JVM中[本地方法接口]的所有内容-pdf

    在Java虚拟机(JVM)的设计中,本地方法接口(Native Method Interface,JNI)是一个至关重要的组成部分,它允许Java代码调用由其他编程语言(如C、C++)编写的函数,实现Java与非Java环境的交互。这个特性极大地...

    java基础常识与概念

    静态方法为什么不能调用非静态成员? 静态方法和实例方法有何不同? 重载和重写的区别 什么是可变长参数? 基本数据类型 Java 中的几种基本数据类型了解么? 基本类型和包装类型的区别? 包装类型的缓存机制了解么? ...

    jvm 启动过程 JVM 原理

    - **本地方法栈**:与Java方法不同,本地方法栈为JNI(Java Native Interface)调用的C/C++等本地方法服务。 了解JVM的启动过程和工作原理对于优化Java程序性能至关重要。通过调整JVM参数,我们可以控制堆大小、...

    远程方法调用(客户端调用服务端的方法)源码

    远程方法调用(Remote Method Invocation,简称RMI)是Java平台提供的一种机制,它允许一个程序在不同的Java虚拟机(JVM)之间调用另一个程序的方法。这种技术使得分布式计算成为可能,使得开发者可以构建分布式应用...

    jvm 详细介绍,了解jvm各个组成部分和功能

    JVM 的本地接口(JNI)允许 Java 程序直接调用本地方法(通常是 C 或 C++ 编写的函数)。这为 Java 应用提供了与底层操作系统和硬件交互的能力,同时也增强了 Java 程序的性能和灵活性。通过 JNI,开发人员可以在...

    java之jvm学习笔记十二(访问控制器的栈校验机制)

    4. 访问控制:栈校验还会检查类、接口、字段和方法的访问权限。例如,私有成员只能在定义它们的类内部访问,如果尝试从外部访问,JVM会抛出异常。 5. 对象状态:对于对象实例的访问,栈校验会确保对象已经被初始化...

    16.方法调用1

    Java虚拟机(JVM)提供了多种方法调用指令,每种都有其特定的用途和行为。 1. **invokestatic** - 这条指令用于调用静态方法,这些方法与类直接关联,而非实例对象。静态方法在编译时就已经确定了调用的目标,因此...

    JVM方法执行的来龙去脉 - 简书1

    在JVM内部,有一个名为`JavaCalls`的模块,它负责处理Java方法之间的调用。`JavaCalls`包含了多种函数,如`call_virtual()`、`call_special()`、`call_static()`等,它们分别对应于Java中的虚方法调用、私有方法调用...

    易语言JAVA调用 例子

    3. **调用Java方法**:使用`CallStaticVoidMethod`或`CallVoidMethod`等函数,根据方法是否为静态,以及传入的参数类型,调用Java方法。 4. **处理结果**:根据Java方法的返回值,易语言程序可以进行相应的操作。 ...

Global site tag (gtag.js) - Google Analytics