1.代码的编译
CLR(Common Language Runtime),公共语言运行时,顾名思义,就是可以由多种语言使用的运行时,当我们用vs新建一个源码文件,写好代码,然后编译的时候,实际会生成一个托管模块,这个托管模块是一个标准的windows PE32文件,或者是一个标准的windows PE32+文件,该模块的组成部分为:
组成部分 |
说明 |
PE32 或PE32+头 |
标准windows PE文件头;如果这个头使用PE32格式,文件能在windows的32位或者64位版本上运行,如果这个头使用的PE32+格式,文件只能在windows 64位版本上运行 |
CLR头 |
包含使这个模块成为一个托管模块的信息 |
元数据 |
每个托管模块都包含元数据表,主要有两种类型的表:一种是描述源代码中定义的类型和成员,另一种是藐视源代码引用的类型和成员 |
IL代码 |
编译器编译源代码生成的代码,在运行时,CLR会将IL代码编译成本地CPU指令 |
默认情况下,编译器会将托管模块转换为程序集。
2.代码的执行
托管程序集中同时包含元数据和IL,IL是一种与CPU无关的机器语言,当我们的程序首次执行时,会把要执行的IL代码编译成本地CPU指令;这是CLR的JIT编译器的职责。
举个例子,如下代码第一次执行:
static void Main(string[] args) { Console.WriteLine("This is first executing..."); Console.WriteLine("This is second executing..."); }
在Main方法执行前,CLR会检测Main方法中所有引用的类型。这将导致CLR分配一个内部数据结构,它用于管理对所有引用的类型的访问。在Main中,有对Console类型的引用,将分配一个 内部数据结构,在这个数据结构中,Console的每一个方法都有一个对应的记录项,每一个记录项都容纳了一个可以找到方法实现的地址。对这个数据结构进行初始化时,CLR将每个记录项都设置成包含在CLR内部的一个未文档话的函数,我们称之为JITCompiler。
Main方法第一次执行并调用Console.WriteLine方法时,JITCompiler函数被调用时,它知道要调用的是哪个方法,然后就会找到该方法的IL代码,验证并将其转换为本地CPU指令,指令会被存到一个动态分配的内存块中,然后JITCompiler返回CLR为类型创建的内部数据结构,找到与被调用方法对应的记录项,修改对JITCompiler的引用为刚转换的CPU指令内存块的地址;最后JITCompiler会跳转到内存块中的代码,执行完毕后就返回并继续执行Main中下面的代码。
当Main中第二次调用Console.WriteLine相同的方法时,将会直接执行内存块中的代码,完全跳过JITCompiler。
所以,一个方法只有在首次调用的时候才会造成一些性能损失,以后的调用都是以本地代码的形式执行,无需验证和编译。
JIT编译器将本地CPU指令存储到动态内存中,所以一旦程序终止编译好的代码也将被丢弃,再次运行程序或同时启动两个应用程序,JIT编译器将再次将IL编译成本地CPU指令。
3.IL的验证
将IL编译成本地CPU指令时,CLR会执行一个名为验证的过程,这个过程会检测高级IL代码,确保代码所做的一切都是安全的,这也确保我们的应用程序的健壮性和安全性。
令:有一点我没有弄清楚的是,当一个类型的一个方法第一次被调用时会被编译成CPU指令,并记录内存地址,那么如果一个方法第一次同时有两个地方调用,比如两个线程,那么会不会被编译两次呢?
鄙人走在求知的道路中,写博客过程中如有不对或者理解错误之处希望大家不吝赐教,吾感激不尽。