- 将源代码编译成托管代码,也就是生成一个个PE文件。
- 将托管模块合并成程序集。根据各个托管模块中的元数据生成一个或者多个模块的逻辑性分组(一般编译器会默认将多个模块生成程序集)。取决于/platform开关选项,程序集要么包含一个PE32文件,要么包含一个PE32+文件。一般情况下,编译器默认情况下是anycpu,也就是说,只要都是托管模块,不管是32位系统还是64位系统,代码都是一样的,不需要进行任何改变。如果包含非托管代码就另说了。另外,也可以在编译器上手东更改目标平台。。不过一般很少这样做而已。
- 加载CLR。生成程序集时,编译器其实在其PE文件中指定了CPU架构,而在执行可执行文件的时候,windows会检查应用程序文件的头,检查当前CPU是否满足运行条件。
- 执行程序集代码:生成程序集的时候,已经生成IL代码。但是,要想本地系统执行,还是要将IL转换成CPU指令。(这由CLR的JIT编译器来执行)。
一个方法第一次执行时:
- 由于.net是面向类型的,所以,在执行main函数之前,CLR会检测出main函数中的所有类型,并为所有的类型分别分配一个内部数据结构,这个内部数据结构中,该类型定义的每个方法都有一个对应的记录项。每个记录项都容纳了一个地址,根据地址可找到该方法的实现。这个数据结构刚初始化时,CLR将每个记录项都设置(指向)成JIT编译器。方法首次调用时,JIT编译器根据程序集的元数据找到方法的IL代码,并将IL代码编译成CPU指令。此时,本地CPU指令会被存到一个动态内存中,并将动态内存的地址存到之前的数据结构中该方法的记录项中(之前存储的是JIT编译器的地址),再之后,JIT编译器才会内存中的CPU指令执行。执行完再返回到main函数中。依次执行下一条代码。
- 一个方法只有在第一次执行时会造成一些性能损失,以后再次执行,都会直接运行编译好的CPU指令。不需要重新编译。
- 一旦程序运行结束,为方法分配的动态内存会被丢弃。也就是说编译好的CPU指令也会被丢弃。
- JIT编译器编译生成本地CPU指令的时候,会对本地代码进行优化。