- 浏览: 3048959 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (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 22395(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局 (0): 拿在手上的是什么
2013-11-04 18:22 21493(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 ...
相关推荐
MFC是一套由微软提供的C++类库,用于简化Windows API的使用,而CLR是.NET Framework的核心组成部分,它提供了跨语言的运行时环境,支持多种编程语言。 MFC是微软为Windows应用程序设计的面向对象的类库,它封装了...
CLR(Common Language Runtime)是.NET框架的核心组成部分,它为各种编程语言提供了运行环境,使得开发者可以用不同的语言编写代码,然后在CLR上统一运行。在这个"CLR项目开发文档.rar"中,我们可以期待找到关于如何...
- 平台独立:可以在任何支持CLR的平台上运行。 - 可延迟编译:在程序运行时才进行最终编译。 - 支持类型安全性:CLR会在编译过程中进行类型检查。 - **IL的生成过程**: - .NET编译器将源代码编译成IL。 - IL...
1. **CLR(Common Language Runtime)**:CLR是.NET Framework的核心组成部分,它提供了跨语言运行时环境,使得多种编程语言如C++、C#等能在同一平台上运行。CLR执行诸如类型安全检查、内存管理、异常处理等任务。 ...
《框架设计 CLR Via C#》是一本深入探讨.NET框架核心组件——公共语言运行时(Common Language Runtime, CLR)的专业书籍。这本书通过C#语言详细阐述了CLR的工作原理、设计思想以及如何利用它来构建高效、稳定的软件...
综上所述,这个压缩包提供的是一个与.NET Framework的CLR接口相关的C++库,包括了对BIOS和图形功能的头文件引用,以及可能用于在C++中调用.NET服务的C语言函数实现。开发者可以利用这些资源来开发混合模式的应用程序...
《CLR via C#》是微软资深开发人员Jeffrey Richter所著的一本经典书籍,主要讲解.NET Framework的公共语言运行时(Common Language Runtime, CLR)以及如何通过C#语言进行深入理解和利用。这本书的第三版提供了最新...
6. **反射**:反射允许程序在运行时动态地获取类型信息并操作类型。源码将展示如何使用反射创建对象、调用方法和访问属性。 7. **元数据和IL(中间语言)**:.NET程序编译后会产生元数据,这些数据描述了类型和成员...
- **示例**:使用反射,可以在运行时创建对象、调用方法或获取属性值,这对于实现灵活的编程模型非常重要。 #### 八、NameSpace(命名空间) - **定义**:命名空间是.NET框架中用于组织类和其他类型的容器。 - **...
《CLR via C#(第3版) 》针对.NET Framework 4.0和多核编程进行了全面更新和修订,是帮助读者深入探索和掌握公共语言运行时、C#和.NET开发的重要参考,同时也是帮助开发人员构建任何一种应用程序(如Microsoft ...
这使得C#程序可以在多种操作系统上运行,只要安装了.NET框架。 在C#中,类和对象是面向对象编程的基础。类定义了对象的属性(数据成员)和行为(方法)。C#支持封装、继承和多态性这三种面向对象的基本原则。封装...
当接口具有一个或多个显式基接口时,在该接口声明中,接口标识符后跟一个冒号以及由逗号分隔的基接口标识符列表。接口的基接口是显式基接口及其基接口。换言之,基接口集是显式基接口、它们的显式基接口(依此类推)...
通过CLR,不同的编程语言可以在相同的运行时环境中无缝交互,实现跨语言互操作性。 2. "Hello, world"程序 学习任何编程语言的第一步通常是从编写简单的"Hello, world"程序开始。在C#中,这通常涉及定义一个包含...
2. **强类型和类型安全**:CLR提供了强类型检查和类型安全机制,能够防止运行时类型转换错误和内存溢出问题。 3. **异常处理**:CLR支持结构化异常处理,使得错误处理更加规范和高效,避免了T-SQL中的TRY...CATCH块...
- 在调用COM组件时,错误处理是不可或缺的,需要检查接口创建和方法调用的返回状态,确保能够处理异常情况。 - C#创建的托管代码和C++原生代码之间存在差异,需要注意内存管理和异常处理的异同,确保程序的稳定性...
而托管代码则是.NET Framework中的一个概念,指的是运行在.NET CLR(Common Language Runtime)之上的代码。 首先,让我们理解Web服务的基本概念。Web服务是一种基于网络的、松散耦合的软件组件,它通过标准协议如...
当程序启动时,它会调用运行库中的函数来完成特定任务,如内存管理、图形渲染、网络通信等。这样,开发者可以专注于编写应用程序的核心逻辑,而不是重复实现基础功能。 **Windows运行库的重要性:** 1. **兼容性** ...
这意味着,尽管C#运行在公共语言运行时(CLR)上,但通过`DllImport`我们可以与非托管的DLL进行交互。例如,调用User32.dll中的MessageBox函数可以这样写: ```csharp [DllImport("user32.dll", EntryPoint = ...
当你的计算机上缺少某些运行库时,可能会导致一些程序无法正常启动或运行。微软的运行库合集通常包括以下重要组件: 1. Microsoft Visual C++ Redistributable: 这是一系列用于执行使用Visual C++编译器开发的应用...
JIT(Just-In-Time)编译是CLR的另一个重要特性,它会在方法首次运行时将中间语言(IL)编译成机器码。这导致了第一次调用时的性能损失,但之后的调用会快速且高效,因为已经转换成了本地代码。 方法签名是方法的...