论坛程序做导航网站,专做轮胎的网站,钓鱼网站怎样做,温州网站建设推广专家文章目录1 .NET Runtime#xff08;CLR-公共语言运行时#xff09;1.1 中间语言 IL1.1.1 从源代码到通用中间语言#xff08;IL#xff09;1.1.2 运行时加载#xff1a;CLR登场1.1.3 核心步骤#xff1a;即时编译 (JIT Compilation)1.1.4 执行与内存管理#xff08;GCCLR-公共语言运行时1.1 中间语言 IL1.1.1 从源代码到通用中间语言IL1.1.2 运行时加载CLR登场1.1.3 核心步骤即时编译 (JIT Compilation)1.1.4 执行与内存管理GC1.1.5 演进与高级模式分层编译与 AOT1.2 CLR运行原理1.2.1 从源代码到程序集一切的开端1.2.2 运行时加载与初始化1.2.3 JIT 编译从通用 IL 到原生代码1.2.4 执行与管理CLR 的持续服务1.2.5 演进与高级模式核心框架与运行时是.NET的基石决定了能开发什么类型的应用已经如何运行。
1 .NET RuntimeCLR-公共语言运行时
负责执行编译后的代码中间语言IL、内存管理垃圾回收GC、异常处理、线程管理等。是所有.Net应用的引擎
1.1 中间语言 IL .Net运行时(CLR)执行编译后的代码(中间语言,IL)是一个核心过程理解这个过程就能明白跨平台、安全性、高性能等特性的基础。 整个过程可以概括为一下关键阶段
编写源代码与编译为IL分发与部署包含IL的程序集运行时加载与即时编译JIT Compilation执行本地代码优化与高级特性分层编译、AOT
1.1.1 从源代码到通用中间语言IL
当使用 C#、F# 或 VB.NET 编写代码并执行 dotnet build 时发生的事情与 C/C 这样的原生语言完全不同 C/C (原生编译)编译器直接将源代码编译为针对特定 CPU 架构如 x86, ARM和操作系统的本地机器码。这个代码无法在其他平台上运行.NET (托管编译)编译器如 Roslyn for C#会将源代码编译为一种称为 中间语言 (IL) 或 通用中间语言 (CIL) 的字节码。同时它还会生成丰富的元数据描述代码中的类型、成员、引用等信息 IL是什么 可以把IL想象成一种高度抽象、与特定CPU无关的“汇编语言”。它比高级语言更底层但是比真正的机器码更高级。它包含了ldloc加载本地变量、add相加、call调用方法这样的指令 为什么这样做 关键优势跨平台和语言互操作性。IL是一种统一的、标准的输出格式。无论使用的事C#还是F#最终都变成了IL。这使得.NET运行时只需要理解IL这一种语言就能运行所有.NET语言编写的程序。同时因为IL不是特定于某个平台的所以同一个IL程序集.dll或.exe可以分发到任何有相应.NET运行时CLR的平台上Windows、Linux、macOS 1.1.2 运行时加载CLR登场 当运行一个.NET程序时操作系统会启动.NET运行时CLR。CLR的程序集加载器会负责找到并加载程序集以及它所依赖的所有程序集。加载后CLR会读取其中的元数据和IL代码为执行做准备。 1.1.3 核心步骤即时编译 (JIT Compilation)
这是最神奇、最核心的一步CLR不会直接“解释”执行IL(像早期的Java或Python那样)。相反它使用一个名为JIT编译器Just-In-Time Compiler的组件
JIT编译器的工作流程如下
按需编译当一个方法函数第一次被调用时JIT编译器才会开始工作。CLR不会在程序启动时就把所有IL都编译成本地代码这避免了不必要的启动延迟。读取ILJIT编译器从已加载的程序集中获取该方法的IL代码。验证在编译之前JIT会执行一个重要的验证过程。它会检查IL代码是否是类型安全的例如不会错误地将一个整数当做对象引用来使用。这个步骤是.NET内存安全和安全沙箱的基石它能组织大量潜在的内存损坏漏洞。编译为本地代码验证通过后JIT编译器将IL代码动态地编译成当前所在平台的本地机器码x86、x64、ARM等。这个过程考虑了当前的CPU和操作系统环境。存储和执行编译生成的本地机器码被存储在内存中的一块特定的区域通常称为JIT代码堆。然后CLR修改该方法的方法表使其条目指向这块新生成的本地代码。最后程序执行这个刚刚编译好的、极其高效的本地代码。
JIT的优势 跨平台 同一个IL包在Windows上JIT编译为x86代码在Linux上编译为x64代码在Raspberry Pi上编译为ARM代码。 性能优化 JIT编译器可以进行运行时优化。它可以根据程序运行的实际环境进行优化。例如如果它检测到运行程序的CPU支持特定的指令集如AVX2它就可以生成使用这些指令的更高效的代码。静态编译器如C在编译时无法知道程序最终会运行在什么CPU上因此无法做到这一点。 节省内存 只有真正被执行到的代码才会被编译和加载到内存中。
1.1.4 执行与内存管理GC
代码已经是以本地机器码的形式在 CPU 上直接执行了速度非常快。 在执行过程中CLR 的另一个核心组件——垃圾回收器 (Garbage Collector, GC)——会持续工作。它负责自动分配和释放内存。当对象不再被引用时GC 会自动回收它们占用的内存开发者无需也不能手动释放。这消除了内存泄漏和悬空指针等常见问题。
1.1.5 演进与高级模式分层编译与 AOT
最初的 JIT 编译策略是“一次性编译”但现代 .NET.NET Core 3.0引入了更先进的策略
分层编译 (Tiered Compilation)
第一层 (快速 JIT)当一个方法第一次被调用时JIT会快速地进行编译生成优化程度较低但编译速度极快的代码。目标是尽快让程序跑起来第二层 (优化 JIT)如果发现某个方法被频繁调用成为“热路径”CLR会在后台异步地启动一个优化版本的JIT编译器重新编译该方法生成高度优化的、更快的本地代码。之后对该方法的调用就会切换到优化版本上。**好处**完美平衡了启动速度和运行速度。
预先编译 (AOT - Ahead of Time)
虽然JIT很棒但是它的编译过程仍然会在程序运行时产生一些开销CPU和内存。对于某些场景如启动速度极致的App、命令工具我们希望消除这个开销Native AOT.NET提供了Native AOT编译模式。它在发布时就直接将IL代码编译为本地可执行文件完全不需要在目标机器上安装.NET运行时也没有JIT编译阶段结果生成的文件更大启动速度极快但失去了JIT的运行时优化能力。.NET 8和更高版本对Native AOT的支持已经非常完善
总结与类比
步骤.Net托管Java传统原生C/C编译源代码 - 中间语言 (IL) 元数据源代码 - 字节码 (.class)源代码 - 本地机器码 (.exe)分发包含 IL 的程序集跨平台包含字节码的 JAR 文件跨平台特定平台的二进制文件执行CLR JIT 编译为本地代码并执行JVM JIT 编译为本地代码并执行操作系统直接加载执行
可以把一个 .NET 程序想象成
IL 是一份标准化的、与烹饪设备无关的菜谱。CLR 是一位厨师JIT 编译器和一个厨房运行时环境。厨师在接到订单方法调用时根据手头的厨具CPU 架构和食材环境现场Just-In-Time 将菜谱翻译成具体的烹饪步骤本地机器码并做菜执行。
这种方式既保证了菜谱程序的通用性又能让每位厨师不同平台上的 CLR利用自己厨房的最优条件做出最好的菜。
1.2 CLR运行原理
CLR 是一个复杂的执行环境它的核心任务是管理 .NET 代码的执行提供内存管理、线程管理、类型安全、异常处理、安全性等一系列关键服务。我们可以将其运行原理分解为以下几个核心阶段和组件
1.2.1 从源代码到程序集一切的开端
运行程序之前旅程早已开始
编译Compiler用 C#、F# 或 VB.NET 等高级语言编写代码。编译器如 C# 的 Roslyn会将其编译为 中间语言IL / CIL 和元数据Metadata并打包成一个 程序集Assembly通常是 .dll 或 .exe 文件。 IL一种与特定 CPU 无关的、类似汇编的指令集。它比高级语言低级但比原生机器码高级。它是 CLR 的“通用语言”。元数据一个详细的“清单”描述了程序集中的所有类型、方法、属性、依赖关系等。这使得 CLR 和开发工具能够智能地理解代码结构。 部署Deployment分发这个包含 IL 和元数据的程序集。它的一个关键优势是跨平台性——同一个 IL 包可以在任何有对应 .NET 运行时Windows, Linux, macOS的平台上运行。
1.2.2 运行时加载与初始化
双击一个 .NET.exe 或在命令行中启动它时操作系统的加载器会识别出这是一个 .NET 程序并启动相应的CLR。 启动 CLR操作系统加载 coreclr.dll对于 .NET Core/.NET 5或 clr.dll对于 .NET Framework这是 CLR 本身的实现。 创建应用程序域AppDomainCLR 会为应用程序创建一个逻辑隔离容器称为应用程序域。它提供了代码加载、执行和卸载的隔离边界虽然在 .NET Core 中 AppDomain 的隔离性被削弱概念依然存在。 加载程序集CLR 的程序集加载器Loader 找到你的启动程序集EXE及其所有依赖项DLLs并将它们加载到应用程序域中。它使用元数据来解析所有依赖关系。
1.2.3 JIT 编译从通用 IL 到原生代码
这是 CLR 最核心、最精妙的部分。CLR 并不直接解释执行 IL而是采用了一种称为 即时编译Just-In-Time Compilation, JIT 的技术。 按需编译当一个方法函数第一次被调用时CLR 才会介入。 验证Verification在编译之前JIT 编译器会执行一个至关重要的步骤——验证。它会分析该方法的 IL 代码确保它是类型安全的例如不会将整数当作对象引用来错误使用、不会发生缓冲区溢出。这是 .NET 内存安全和稳定性的基石它阻止了绝大多数常见的安全漏洞和程序崩溃。 编译为原生代码验证通过后JIT 编译器将 IL 代码动态地编译成当前运行机器的特定 CPU 架构x86, x64, ARM的生机器码。 存储与执行编译好的原生代码被存储在内存中的一块特定区域JIT 代码堆。CLR 然后会修补该方法的方法表使其条目指向这块新生成的、高效的本地代码。最后程序执行这个刚刚编译好的本地代码。
JIT 的优势 跨平台一份 IL处处编译运行。 性能优化JIT 可以进行运行时优化。它知道程序运行的具体硬件环境CPU 型号、指令集支持可以生成最适合当前机器的优化代码。这是静态编译器如 C无法做到的。 分析引导的优化PGO现代 .NET 的 JIT 甚至可以观察程序的运行 profile哪些分支最常走哪些方法是热路径并进行更激进的优化。
1.2.4 执行与管理CLR 的持续服务
在代码执行过程中CLR 持续提供关键服务就像一个全能的管家 内存管理与垃圾回收GC 分配当使用 new 关键字创建对象时CLR 在托管堆上为其分配内存。托管堆是一块由 CLR 连续管理的内存区域分配速度极快仅需移动一个指针。 回收垃圾回收器Garbage Collector 会自动追踪对象的引用。当堆内存不足时GC 会启动它从“根对象”开始遍历标记所有仍在被引用的存活对象然后回收未被标记的垃圾对象的内存。之后它会压缩存活对象消除内存碎片。这个过程完全是自动的开发者无需关心。 异常处理 CLR 提供了一套结构化的、跨语言的异常处理机制。当异常被抛出时CLR 会中断当前流程遍历调用栈寻找合适的 catch 块来处理异常。如果找不到则终止进程。 线程管理 CLR 提供了线程池ThreadPool高效地管理线程的生命周期避免频繁创建和销毁线程的巨大开销。它也是 async/await 异步编程模型的底层支撑。 安全性 .NET 提供了基于证据的安全性如代码的来源、出版商虽然现在较少使用但其验证系统本身就是一道强大的安全防线阻止不安全的代码执行。 互操作性 通过 P/Invoke平台调用CLR 允许托管代码调用原生 C/C 编写的库DLLs。 通过 COM Interop允许 .NET 与传统的 COM 组件进行交互。
1.2.5 演进与高级模式
CLR 也在不断进化引入了新的编译模式以适应更多场景
ReadyToRun (R2R)一种预先编译Ahead-Of-Time, AOT 的形式。程序集在发布时就被部分编译为本机代码减少了应用程序启动时的 JIT 编译开销从而改善启动性能。它更像是“JIT 预热”的产物。Native AOT.NET 7/8 的重点程序在发布时被完全编译为一个独立的、不依赖 .NET 运行时的原生可执行文件。没有 JIT没有 IL。代价是失去了 JIT 的运行时优化能力并且文件更大但换来了极致的启动速度和更小的部署体积只包含真正用到的代码。这是创建独立命令行工具和资源受限环境的理想选择。 CLR 的精妙之处在于它通过 JIT 编译和托管环境在开发效率自动内存管理、跨平台、类型安全、执行性能JIT 优化、高效内存分配和安全性之间取得了非凡的平衡。它让开发者能从繁琐的底层细节中解放出来专注于实现业务逻辑。