- 浏览: 3015073 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (430)
- Programming Languages (23)
- Compiler (20)
- Virtual Machine (57)
- Garbage Collection (4)
- HotSpot VM (26)
- Mono (2)
- SSCLI Rotor (1)
- Harmony (0)
- DLR (19)
- Ruby (28)
- C# (38)
- F# (3)
- Haskell (0)
- Scheme (1)
- Regular Expression (5)
- Python (4)
- ECMAScript (2)
- JavaScript (18)
- ActionScript (7)
- Squirrel (2)
- C (6)
- C++ (10)
- D (2)
- .NET (13)
- Java (86)
- Scala (1)
- Groovy (3)
- Optimization (6)
- Data Structure and Algorithm (3)
- Books (4)
- WPF (1)
- Game Engines (7)
- 吉里吉里 (12)
- UML (1)
- Reverse Engineering (11)
- NSIS (4)
- Utilities (3)
- Design Patterns (1)
- Visual Studio (9)
- Windows 7 (3)
- x86 Assembler (1)
- Android (2)
- School Assignment / Test (6)
- Anti-virus (1)
- REST (1)
- Profiling (1)
- misc (39)
- NetOA (12)
- rant (6)
- anime (5)
- Links (12)
- CLR (7)
- GC (1)
- OpenJDK (2)
- JVM (4)
- KVM (0)
- Rhino (1)
- LINQ (2)
- JScript (0)
- Nashorn (0)
- Dalvik (1)
- DTrace (0)
- LLVM (0)
- MSIL (0)
最新评论
-
mldxs:
虽然很多还是看不懂,写的很好!
虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩 -
HanyuKing:
Java的多维数组 -
funnyone:
Java 8的default method与method resolution -
ljs_nogard:
Xamarin workbook - .Net Core 中不 ...
LINQ的恶搞…… -
txm119161336:
allocatestlye1 顺序为 // Fields o ...
最近做的两次Java/JVM分享的概要
作者:RednaxelaFX
主页:http://rednaxelafx.iteye.com
日期:2009-06-02
系列笔记:
JVM在校验阶段不检查接口的实现状况
为什么JVM与CLR都不对接口方法调用做静态校验?
刚才的一帖,JVM在校验阶段不检查接口的实现状况,我提到JVM在处理invokeinterface时,如果遇到被调用对象没有实现指定的接口时,在运行时抛出异常的行为。那么.NET的CLR在这方面的行为又如何呢?
CLR对MSIL的校验是与JIT同时进行的。JIT编译器一边检查代码的正确性,一边生成native code;一个方法被调用时,如果还没有JIT过的话,要在JIT之后才进入运行状态。于是CLR执行托管方法可以分为一个[加载-链接-校验-JIT]的阶段与一个[执行]的阶段。与JVM一样,如果CLR遇到被调用对象没有实现指定的接口的状况,也是在运行时抛出异常的。
要观察这个行为也同样需要对MSIL做些操作。先用这个源码来编译得到exe:
TestInterfaceCall.cs
然后利用ILDASM将其反编译为MSIL:
TestInterfaceCall.il
与前一帖一样,这里我们要避免castclass指令干扰测试的结果。把Main()里的IL_0015那行注释掉,然后再用ILASM重新编译为TestInterfaceCall.exe。
运行结果如下:
可以看到,这一点上CLR与JVM的行为一致。
===================================================================
CLR 2.0之前是用全局的接口查找表来做接口方法调用的分发(dispatch)的。到2.0之后,CLR改用基于stub的方式来做接口方法调用分发。对接口方法的调用点,JIT一开始会生成一个间接调用指向一个lookup stub,后者又指向一个resolver stub,用于查找实际的方法实现;对同一个被调用对象类型成功调用2次之后会生成特化于那个类型的dispatch stub(是一个monomorphic inline cache),然后记录该stub调用失败的次数,达到100次之后就退化回到非特化的resolver stub。
如果被调用对象没有实现指定的接口,则在调用点初次被调用时,进到resolver stub之后会发现找不到接口方法的具体实现,然后就抛出异常。
===================================================================
值得注意的是,这两帖里提到的“接口方法调用”都是指正常的、直接的调用,而不是通过反射去做的调用。如果把这帖里的代码例子中第9行的((IFoo)b).Method();改为反射调用:
则编译和校验都不会出现问题,而运行时抛出的异常会是这样的:
与直接在IL里去调用接口方法不同。这是因为反射本身就是通过托管代码实现的,也就是说在进到虚拟机底层之前,在托管代码里就已经做了很多检查,这些检查发现所要调用的方法与实际提供的被调用对象不匹配,于是在托管代码的层次上就抛出了异常。
对,MSIL里都是callvirt,但JIT的时候得到了不同的处理:
对虚方法的分发是编译成这样:
对接口方法的分发是编译成:
这个call是对一个固定地址做间接调用。一开始是调用到一个通用的resolver stub,去找具体的方法的地址。如果在同一个调用点出现两次相同类型的被调用对象,则间接调用会指向为该类型特化的一个dispatch stub上,样子类似这样:
这段代码被称为monomorphic inline method cache,怎么翻译好呢……单态内联方法缓存?
比较失败时会跳转到一个resolver stub;它会维护一个“不命中计数器”。如果在某个调用点累计失败了100次,就会再次更新之前的间接调用为直接指向通用的resolver stub。
这样,如果在一个接口方法的调用点上总是同一个类型的实例被调用,则分发效率跟虚方法调用差不多快。如果被调用对象的类型经常变,速度就会慢下来了……
主页:http://rednaxelafx.iteye.com
日期:2009-06-02
系列笔记:
JVM在校验阶段不检查接口的实现状况
为什么JVM与CLR都不对接口方法调用做静态校验?
刚才的一帖,JVM在校验阶段不检查接口的实现状况,我提到JVM在处理invokeinterface时,如果遇到被调用对象没有实现指定的接口时,在运行时抛出异常的行为。那么.NET的CLR在这方面的行为又如何呢?
CLR对MSIL的校验是与JIT同时进行的。JIT编译器一边检查代码的正确性,一边生成native code;一个方法被调用时,如果还没有JIT过的话,要在JIT之后才进入运行状态。于是CLR执行托管方法可以分为一个[加载-链接-校验-JIT]的阶段与一个[执行]的阶段。与JVM一样,如果CLR遇到被调用对象没有实现指定的接口的状况,也是在运行时抛出异常的。
要观察这个行为也同样需要对MSIL做些操作。先用这个源码来编译得到exe:
TestInterfaceCall.cs
using System; static class TestInterfaceCall { static void Main(string[] args) { IFoo f = new FooImpl(); f.Method(); Bar b = new Bar(); ((IFoo)b).Method(); // << watch this } } public interface IFoo { void Method(); } public class FooImpl : IFoo { public virtual void Method() { Console.WriteLine("FooImpl.Method()"); } } public class Bar { public virtual void AnotherMethod() { Console.WriteLine("Bar.AnotherMethod()"); } }
然后利用ILDASM将其反编译为MSIL:
TestInterfaceCall.il
// Microsoft (R) .NET Framework IL Disassembler. Version 3.5.30729.1 // Copyright (c) Microsoft Corporation. All rights reserved. // Metadata version: v2.0.50727 .assembly extern mscorlib { .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. .ver 2:0:0:0 } .assembly TestInterfaceCall { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows. .hash algorithm 0x00008004 .ver 0:0:0:0 } .module TestInterfaceCall.exe // MVID: {075D6351-0AF0-459F-A822-40E817B58699} .imagebase 0x00400000 .file alignment 0x00000200 .stackreserve 0x00100000 .subsystem 0x0003 // WINDOWS_CUI .corflags 0x00000001 // ILONLY // Image base: 0x03010000 // =============== CLASS MEMBERS DECLARATION =================== .class private abstract auto ansi sealed beforefieldinit TestInterfaceCall extends [mscorlib]System.Object { .method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 33 (0x21) .maxstack 1 .locals init (class IFoo V_0, class Bar V_1) IL_0000: nop IL_0001: newobj instance void FooImpl::.ctor() IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: callvirt instance void IFoo::Method() IL_000d: nop IL_000e: newobj instance void Bar::.ctor() IL_0013: stloc.1 IL_0014: ldloc.1 IL_0015: castclass IFoo IL_001a: callvirt instance void IFoo::Method() IL_001f: nop IL_0020: ret } // end of method TestInterfaceCall::Main } // end of class TestInterfaceCall .class interface public abstract auto ansi IFoo { .method public hidebysig newslot abstract virtual instance void Method() cil managed { } // end of method IFoo::Method } // end of class IFoo .class public auto ansi beforefieldinit FooImpl extends [mscorlib]System.Object implements IFoo { .method public hidebysig newslot virtual instance void Method() cil managed { // Code size 13 (0xd) .maxstack 8 IL_0000: nop IL_0001: ldstr "FooImpl.Method()" IL_0006: call void [mscorlib]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret } // end of method FooImpl::Method .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret } // end of method FooImpl::.ctor } // end of class FooImpl .class public auto ansi beforefieldinit Bar extends [mscorlib]System.Object { .method public hidebysig newslot virtual instance void AnotherMethod() cil managed { // Code size 13 (0xd) .maxstack 8 IL_0000: nop IL_0001: ldstr "Bar.AnotherMethod()" IL_0006: call void [mscorlib]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret } // end of method Bar::AnotherMethod .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret } // end of method Bar::.ctor } // end of class Bar // ============================================================= // *********** DISASSEMBLY COMPLETE *********************** // WARNING: Created Win32 resource file D:\experiment\test\TestInterfaceCall.res
与前一帖一样,这里我们要避免castclass指令干扰测试的结果。把Main()里的IL_0015那行注释掉,然后再用ILASM重新编译为TestInterfaceCall.exe。
运行结果如下:
D:\experiment\test>TestInterfaceCall.exe FooImpl.Method() Unhandled Exception: System.EntryPointNotFoundException: Entry point was not found. at IFoo.Method() at TestInterfaceCall.Main(String[] args)
可以看到,这一点上CLR与JVM的行为一致。
===================================================================
CLR 2.0之前是用全局的接口查找表来做接口方法调用的分发(dispatch)的。到2.0之后,CLR改用基于stub的方式来做接口方法调用分发。对接口方法的调用点,JIT一开始会生成一个间接调用指向一个lookup stub,后者又指向一个resolver stub,用于查找实际的方法实现;对同一个被调用对象类型成功调用2次之后会生成特化于那个类型的dispatch stub(是一个monomorphic inline cache),然后记录该stub调用失败的次数,达到100次之后就退化回到非特化的resolver stub。
如果被调用对象没有实现指定的接口,则在调用点初次被调用时,进到resolver stub之后会发现找不到接口方法的具体实现,然后就抛出异常。
===================================================================
值得注意的是,这两帖里提到的“接口方法调用”都是指正常的、直接的调用,而不是通过反射去做的调用。如果把这帖里的代码例子中第9行的((IFoo)b).Method();改为反射调用:
typeof(IFoo).GetMethod("Method").Invoke(b, new object[0]);
则编译和校验都不会出现问题,而运行时抛出的异常会是这样的:
Unhandled Exception: System.Reflection.TargetException: Object does not match target type. at System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at TestInterfaceCall.Main(String[] args)
与直接在IL里去调用接口方法不同。这是因为反射本身就是通过托管代码实现的,也就是说在进到虚拟机底层之前,在托管代码里就已经做了很多检查,这些检查发现所要调用的方法与实际提供的被调用对象不匹配,于是在托管代码的层次上就抛出了异常。
评论
2 楼
RednaxelaFX
2009-06-03
幸存者 写道
不知接口方法和虚方法分发有什么区别?似乎在CIL中都是callvirt指令。
对,MSIL里都是callvirt,但JIT的时候得到了不同的处理:
对虚方法的分发是编译成这样:
mov ecx, esi ; 假设现在ESI是一个指向对象实例的指针,复制到ECX里 mov eax, dword ptr [ecx] ; 对象实例的第一项是指向方法表的指针,复制到EAX里 call dword ptr [eax + 7Ch] ; EAX现在指向方法表,0x7C是我随便写的一个偏移量。这个在加载类的时候就可以确定
对接口方法的分发是编译成:
mov ecx, esi ; 跟上面一样,ESI是指向对象的指针,复制到ECX call dword ptr [0099EEA0h] ; JIT的时候指向一个固定地址的stub(这里数字是随便编的,别在意)
这个call是对一个固定地址做间接调用。一开始是调用到一个通用的resolver stub,去找具体的方法的地址。如果在同一个调用点出现两次相同类型的被调用对象,则间接调用会指向为该类型特化的一个dispatch stub上,样子类似这样:
cmp dword ptr [ecx], 009A3377h ; 后面的常量是特定类型的方法表地址 jne 0091A012 ; 比较不相等的话,跳转到一个特定的resolver stub上 jmp 00EBD070 ; 相等的话则直接跳转到目标方法的地址
这段代码被称为monomorphic inline method cache,怎么翻译好呢……单态内联方法缓存?
比较失败时会跳转到一个resolver stub;它会维护一个“不命中计数器”。如果在某个调用点累计失败了100次,就会再次更新之前的间接调用为直接指向通用的resolver stub。
这样,如果在一个接口方法的调用点上总是同一个类型的实例被调用,则分发效率跟虚方法调用差不多快。如果被调用对象的类型经常变,速度就会慢下来了……
1 楼
幸存者
2009-06-03
不知接口方法和虚方法分发有什么区别?似乎在CIL中都是callvirt指令。
发表评论
-
The Prehistory of Java, HotSpot and Train
2014-06-02 08:18 0http://cs.gmu.edu/cne/itcore/vi ... -
MSJVM and Sun 1.0.x/1.1.x
2014-05-20 18:50 0当年的survey paper: http://www.sym ... -
Sun JDK1.4.2_28有TieredCompilation
2014-05-12 08:48 0原来以前Sun的JDK 1.4.2 update 28就已经有 ... -
IBM JVM notes (2014 ver)
2014-05-11 07:16 0Sovereign JIT http://publib.bou ... -
class data sharing by Apple
2014-03-28 05:17 0class data sharing is implement ... -
HotSpot Server VM与Server Class Machine
2014-02-18 13:21 0HotSpot VM历来有Client VM与Server V ... -
Java 8的lambda表达式在OpenJDK8中的实现
2014-02-04 12:08 0三月份JDK8就要发布首发了,现在JDK8 release c ... -
GC stack map与deopt stack map的异同
2014-01-08 09:56 0两者之间不并存在包含关系。它们有交集,但也各自有特别的地方。 ... -
HotSpot Server Compiler与data-flow analysis
2014-01-07 17:41 0http://en.wikipedia.org/wiki/Da ... -
基于LLVM实现VM的JIT的一些痛点
2014-01-07 17:25 0同事Philip Reames Sanjoy Das http ... -
tailcall notes
2013-12-27 07:42 0http://blogs.msdn.com/b/clrcode ... -
《自制编程语言》的一些笔记
2013-11-24 00:20 0http://kmaebashi.com/programmer ... -
字符串的一般封装方式的内存布局 (1): 元数据与字符串内容,整体还是分离?
2013-11-07 17:44 22246(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局 (0): 拿在手上的是什么
2013-11-04 18:22 21364(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局
2013-11-01 12:55 0(Disclaimer:未经许可请 ... -
关于string,内存布局,C++ std::string,CoW
2013-10-30 20:45 0(Disclaimer:未经许可请 ... -
Java的instanceof是如何实现的
2013-09-22 16:57 0Java语言规范,Java SE 7版 http://docs ... -
也谈类型: 数据, 类型, 标签
2013-08-18 01:59 0numeric tower http://en.wikiped ... -
oop、klass、handle的关系
2013-07-30 17:34 0oopDesc及其子类的实例 oop : oopDesc* ... -
Nashorn各种笔记
2013-07-15 17:03 0http://bits.netbeans.org/netbea ...
相关推荐
《CLR via C#(第3版) 》针对.NET Framework 4.0和多核编程进行了全面更新和修订,是帮助读者深入探索和掌握公共语言运行时、C#和.NET开发的重要参考,同时也是帮助开发人员构建任何一种应用程序(如Microsoft ...
当接口具有一个或多个显式基接口时,在该接口声明中,接口标识符后跟一个冒号以及由逗号分隔的基接口标识符列表。接口的基接口是显式基接口及其基接口。换言之,基接口集是显式基接口、它们的显式基接口(依此类推)...
《CLR via C#(第3版) 》针对.NET Framework 4.0和多核编程进行了全面更新和修订,是帮助读者深入探索和掌握公共语言运行时、C#和.NET开发的重要参考,同时也是帮助开发人员构建任何一种应用程序(如Microsoft ...
《CLR via C#(第3版) 》针对.NET Framework 4.0和多核编程进行了全面更新和修订,是帮助读者深入探索和掌握公共语言运行时、C#和.NET开发的重要参考,同时也是帮助开发人员构建任何一种应用程序(如Microsoft ...
《CLR via C#(第3版) 》针对.NET Framework 4.0和多核编程进行了全面更新和修订,是帮助读者深入探索和掌握公共语言运行时、C#和.NET开发的重要参考,同时也是帮助开发人员构建任何一种应用程序(如Microsoft ...
2.2 公用语言运行时环境与公用语言规范.13 2.3 开 发 工 具 .17 2.4 小 结 .19 第三章 编写第一个应用程序 .20 3.1 Welcome 程序 .20 3.2 代 码 分 析 .20 3.3 运 行 程 序 .23 .4 添 加 注 释 .25 ...
本页内容 简介 谨慎地进行堆栈遍历 同步和异步调用 总结 做出最佳的表现 够了够了简介 本文面向的是对构建用于检查托管应用程序的分析器感兴趣的读者。我将描述如何编写分析器,以在.NETFramework的公共语言...
修改BUG:超级列表框在属性“整行选择”为真时,鼠标单击第一列右面也会导致第一列中的选择框被选中或取消选中。 21. 修改BUG:Sqlite3数据库支持库中“Sqlite数据库.取错误文本()”返回的文本是UTF-8编码(应是GB...
1. C语言和汇编语言在开发单片机时各有哪些优缺点? 答:汇编语言是一种用文字助记符来表示机器指令的符号语言,是最接近机器码的一种语言。其主要优点是占用资源少、程序执行效率高。但是不同的CPU,其汇编语言...
在个人实践中,Ivor Horton也是一名系统顾问。他从事程序设计教学工作已经超过了25年。 苏正泉,1995年毕业于解放军信息工程学院计算机及应用专业,高级工程师。在IT项目管理、软件开发、系统管理和网络管理方面都...
一般说来,数字系统中运行的电信号,其大小往往并不改变,但在实践分布上 却有着严格的要求,这是数字电路的一个特点。 2 系统的总体设计: 2.1 原理设计 本频率计的设计以AT89S52 单片机为核心,利用它内部的定时/...
1.6 Visual C++ 2010 clr简介 13 1.7 Visual C++ 2010 64位编程 14 1.8 支持新的C++语言标准 14 1.8.1 支持新的C++语言标准(C++ 0x) 14 1.8.2 Lambda表达式 15 1.8.3 静态断言static_assert 17 1.8.4 auto关键字 18 ...
他曾在IBM工作多年,能使用多种语言进行编程(在多种机器上使用汇编语言和高级语言),设计和实现了实时闭环工业控制系统。Horton拥有丰富的教学经验(教学内容包括C、C++、Fortran、PL/1、APL等),同时还是机械、加工...
10.6.1 检查自由存储器的函数 10.6.2 控制自由存储器的调试操作 10.6.3 自由存储器的调试输出 10.7 调试C++/CLI程序 10.8 小结 第11章 Windows编程的概念 11.1 Windows编程基础 11.1.1窗口的元素 11.1.2 Windows程序...
10.6.1 检查自由存储器的函数 10.6.2 控制自由存储器的调试操作 10.6.3 自由存储器的调试输出 10.7 调试C++/CLI程序 10.8 小结 第11章 Windows编程的概念 11.1 Windows编程基础 11.1.1窗口的元素 11.1.2 Windows程序...
10.6.1 检查自由存储器的函数 10.6.2 控制自由存储器的调试操作 10.6.3 自由存储器的调试输出 10.7 调试C++/CLI程序 10.8 小结 第11章 Windows编程的概念 11.1 Windows编程基础 11.1.1窗口的元素 11.1.2 Windows程序...
10.6.1 检查自由存储器的函数 10.6.2 控制自由存储器的调试操作 10.6.3 自由存储器的调试输出 10.7 调试C++/CLI程序 10.8 小结 第11章 Windows编程的概念 11.1 Windows编程基础 11.1.1窗口的元素 11.1.2 Windows程序...
10.6.1 检查自由存储器的函数 10.6.2 控制自由存储器的调试操作 10.6.3 自由存储器的调试输出 10.7 调试C++/CLI程序 10.8 小结 第11章 Windows编程的概念 11.1 Windows编程基础 11.1.1窗口的元素 11.1.2 Windows程序...
10.6.1 检查自由存储器的函数 10.6.2 控制自由存储器的调试操作 10.6.3 自由存储器的调试输出 10.7 调试C++/CLI程序 10.8 小结 第11章 Windows编程的概念 11.1 Windows编程基础 11.1.1窗口的元素 11.1.2 Windows程序...