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

xiaoxiao2026-09-04  10

作者:RednaxelaFX 主页:[url]http://rednaxelafx.iteye.com[/url] 日期:2009-06-02 系列笔记: [url=http://rednaxelafx.iteye.com/blog/400362]JVM在校验阶段不检查接口的实现状况[/url] [url=http://rednaxelafx.iteye.com/blog/400452]CLR上的接口调用也是在运行时检查的 [/url] 前面两帖我们看到JVM与CLR这两个主流的高级语言虚拟机都对接口方法调用管得很松,不对其被调用对象做静态类型校验。可是为什么呢? ================================================================== 这个问题先放在一边,让我们来看看如果让JVM执行一段字节码,其中调用了不存在的虚方法会怎样。还是用bitescript来生成class文件: require 'rubygems'require 'bitescript'include BiteScriptJObject = java.lang.ObjectJString = java.lang.Stringfb = 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 endendfb.generate do |filename, class_builder| File.open(filename, 'w') do |file| file.write(class_builder.generate) endend 执行这段脚本生成TestVirtualCall.class,执行之,会看到: Exception in thread "main" java.lang.VerifyError: (class: TestVirtualCall, method: main signature: ([Ljava/lang/String;)V) Incompatible object argument for function callCould 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;dupinvokespecial java/util/ArrayList."<init>":()Vcheckcast java/lang/Object // << ensures the JVM think local 1 is of type Objectastore 1// o.size(); // note that o actually points to an instance of ArrayListaload 1invokevirtual 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取自[url=http://rednaxelafx.iteye.com/blog/400362]前一帖[/url]的例子: require 'rubygems'require 'bitescript'include BiteScriptIFoo = Java::IFooFooImpl = Java::FooImplfb = 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 endendfb.generate do |filename, class_builder| File.open(filename, 'w') do |file| file.write(class_builder.generate) endend 这段脚本生成的TestVirtualCall2.class执行正常,打印出"FooImpl.method()"。 与前面调用虚方法的例子对比,可以看到JVM对接口方法调用的静态检查确实松一些。结合前一帖的例子看,这仍然是类型安全的,虽然要推迟到运行时才能检查。 ================================================================== 值得注意的是,以上讨论都是以静态类型系统为前提的。如果类的成员能动态的被任意添加删除修改,那接口或基类所定下的契约就不能保证被实现,也就失去了上述讨论的前提。 相关资源:敏捷开发V1.0.pptx
转载请注明原文地址: https://www.6miu.com/read-5052403.html

最新回复(0)