我眼中的IL, JIT,CTS, CLS,CLR
一 概念及作用
1. IL代码:
1)概念:.NET框架中的中间语言(Intermediate Language)的缩写,IL还有另外的2种叫法:CIL,Common Intermediate Language;MSIL, MicrosoftIntermediate Language。
作用:使用.NET框架提供的编译器(例如VS)可以直接将源程序(例如C++,VB.NET,C#)编译为.exe或.dll文件,但此时编译出来的程序代码并不是CPU能直接执行的机器代码,而是一种中间语言IL(Intermediate Language)的代码。因此还需要通过JIT编译器进行二次编译,将IL代码转化成机器代码。
2)优点:使用中间语言的优点有两点,一是可以实现平台无关性,即与特定CPU无关,意思是对平台没有依赖性,不指定特定的CPU,可以跨平台跨系统,具有一定通用性;二是只要把.NET框架某种语言编译成IL代码,就实现.NET框架中语言之间的交互操作。
3)IL与程序集的关系:IL代码是程序集的核心部分。
4).NET程序集的构成
从结构上看,一个.NET程序集(*.dll或者*.exe)包含以下几个部分:
Windows文件首部
Windows文件首部使程序集被Windows系列操作系统加载和操作。这些首部信息标识了应用程序将以什么类型(是基于控制台、基于图形用户界面还是*.dll代码库)驻留在Windows操作系统中。
CLR文件首部
为了驻留于CLR中,所有的.NET文件都必须含有CLR首部数据块,简单地讲,CLR文件首部定义了多个标记,它们使得运行时环境可以了解到托管文件的布局。
IL代码
IL代码是程序集的核心部分,是独立于平台和CPU的中间语言。在运行时,程序集内部的IL代码只在绝对必需的情况下才被(实时的JIT编译器)编译成特定平台和CPU的指令。”绝对必需”通常是指一段IL指令(例如一个方法实现)被CLR引用时。在这种机制下,.NET程序集可以在多种不同的架构、设备和操作系统下运行。
类型元数据
类型元数据完整地描述了程序集内含类型和引用外部类型的格式。.NET运行时环境利用元数据在内存的二进制布局类型中解析类型(以及类型的成员)的位置,使远程方法调用更便利。
程序集清单
清单(manifest,也称程序集元数据,是对程序集本身的自描述)详细记录了程序集中的每一个模块、构建程序集的版本以及该程序集引用的所有外部程序集。它提供有关程序集的类型、版本、区域性和安全要求等信息。
可选的嵌入资源
.NET程序集还可以包含一些嵌入资源,如应用程序图标、图像文件、声音片段或者字符串表。事实上,.NET平台支持卫星程序集(satellite assembly),这些程序集只包含本地化资源。在构建国际化软件系统的时候,我们可能想基于特定区域(英语、德语等)来对资源进行分类打包,这时候附属程序集就显得非常有用。
5)程序集加载流程:
IL 代码与资源(例如位图和字符串)一起作为一种称为程序集的可执行文件存储在磁盘上,通常具有的扩展名为 .exe 或.dll。
执行 C# 程序时,程序集将加载到 CLR 中,这可能会根据清单中的信息执行不同的操作。然后,如果符合安全要求,CLR 就会执行实时 (JIT) 编译以将 IL 代码转换为本机机器指令。
2. JIT编译器:
概念:JIT编译器,英文写作Just-In-Time Compiler,中文意思是即时编译器。
作用:JIT编译器能够将MSIL代码进行二次编译后成为各种不同的机器代码,以适应对应的系统和平台,最终使得程序在目标系统中得到顺利地运行。(帮助IL实现跨系统)
3. CTS: 公共类型系统(Common Type System)
CTS(通用类型系统)是一个正式的规范,它规定了类型必须如何定义才能被CLR承载。通过正式的规范来描述类型的定义和行为,所有.net框架下的目标语言定义的语言都要与CTS的类型进行映射对应,这样才能保证各语言之间的互操作性。
4.CLS:公共语言规范(Common Language Specification)
CLS限制了由各种语言不同特性而引发的互操作性问题,CLS制定了一种以.NET平台为目标的语言所必须支持的最小特征,
以及该语言与其他.NET语言之间实现互操作性所需要的完备特征。即.NET框架下的目标语言如果要实现互操作性,至少需要对CLS规范进行完全支持。
由图总结:
1) CTS定义类型,CLS定义规范,在共同定义的环境下实现以.Net平台为目标的各语言之间的互操作性。
2)CLS是各语言间实现互操作性的最低规范。但是内部使用语言时可以只满足CTS不满足CLS。
3) CRL严格按照CTS进行实现。
5.CLR:公共语言运行时(Common Language RunTime)
概念:是.NET提供的一种运行环境,可以被支持.NET的所有语言和平台所使用。它负责执行用.NET语言开发的代码(可以理解为托管代码的操作系统)。CLR是适用于所有.NET语言的运行库。它用于执行和管理用任何一种针对.NET平台的语言编写的所有代码。
CLR负责的工作
1)加载.NET执行引擎(读取程序集):
CLR中最重要的部分是由名为mscoree.dll的库(又称公共对象运行时执行引擎)物理表示的。当用户程序引用一个程序集,要使用它时,mscoree.dll将首先自动加载,然后由它负责将需要的程序集导入内存。运行时引擎负责许多任务,首要的任务是负责解析程序集的位置,并通过读取其中包含的元数据,在二进制文件中发现所请求的类型。接着,CLR在内存中为类型布局(内存分配),将关联的CIL编译成特定平台的机器指令,执行所有需要的安全检查,然后运行当前的代码。
2)加载.NET基础类库(类加载器):
CLR会与包含在.NET基础类库的类型进行交互。虽然完整的基础类库被分为若干分离的程序集,但最重要的程序集是mscorlib.dll。mscorlib.dll包含大量核心类型,它们封装了各种常见的编程任务与.NET语言用到的核心数据类型。当建立一个.NET解决方案时,你可以自动访问这些程序集。
3) 内存管理(代码托管,内存分配,GC垃圾回收机制,字符串内存特殊管理):
a.程序托管与内存管理:C#中应用程序不直接与操作系统(操作系统负责分配内存)打交道,应用程序先向CLR发请求,CLR再向操作系统请求分配内存。当内存不再使用时,应用程序无需告知CLR,由CLR发现内存不再引用,便会自动进行销毁,这就是托管的含义(CLR负责内存管理) 好处在于不用操心内存管理;坏处是过分依赖;
b. 字符串内存特殊管理,是通过字符串拘留池(重复字符串的优化):程序中大量使用字符串,导致不少是重复的,为了降低内存占用,.NET的CRL可能会将代码中声明的字符串放到字符串拘留池中,由于字符串的不可变性,能保证实例的值在存储时具有稳定性(值不会被改变),因此可以将值相同的字符串共同指向同一个实例对象。并不是所有字符串都在拘留池中,.NET的CLR会判断哪些该放。
场景1:
String S1 = “abc”;
String S2 = ”abc”;
bool b = object.ReferenceEquals(s1,s2);
b为true
解读:原本应该会是两个对象,但是由于字符串拘留池的存在, CLR进行了类似于重用处理,将值相同的字符串只产生一个string对象,大家共同指向这个对象,可以减少对内存的使用。因此该情况只针对于字符串类型成立。
场景2:
String S1 = “abc”;
String S2 = new string(”abc”.ToCharArry());
bool b = object.ReferenceEquals(s1,s2);
b为false
解读:该场景下创建了两个对象,因为只要通过new的方式,就一定会开辟新的内存空间,以此CRL就不能再进行重用优化了,变量存的地址指向不同的对象。
区别:只有固定生成的字符串值才能被CRL进行重用优化,进入字符串拘留池。动态生成的字符串值(new的方式)则没法被CLR优化,不会在字符串拘留池中。
拓展:
只有在代码区中写的值内容才会有可能进字符串拘留池。
String S1 = “abc”;
String a1 = console.ReadLine();//输入a
String a2= console.ReadLine();//输入bc
String S2 = a1+a2;
bool b = object.ReferenceEquals(s1,s2);
b为false
4)CLR执行流程总结:
一图以蔽之: