C#程序的生命周期:从CLR加载到GC回收

1. CLR加载阶段

当操作系统启动C#程序时:

  • 程序集加载:CLR加载主程序集(.exe)及其依赖项(.dll)
  • 元数据解析:读取程序集中的元数据表(类型、方法、字段等)
  • 应用程序域创建:建立隔离的执行环境
  • 入口点定位:找到Main()方法入口

流程示意:
$ \text{OS启动} \rightarrow \text{CLR初始化} \rightarrow \text{程序集加载} \rightarrow \text{元数据解析} \rightarrow \text{AppDomain创建} $

2. JIT编译阶段

CLR通过即时编译转换MSIL代码:

// MSIL代码示例(简化)
ldarg.0       // 加载参数
call void Console::WriteLine(string)
ret

  • 方法桩(Stub):首次调用方法时插入跳转指令
  • 编译过程
    $ \text{MSIL} \xrightarrow{\text{JIT}} \text{原生机器码} $
  • 代码缓存:编译后的机器码存入内存,后续调用直接执行
3. 执行阶段
  • 线程管理:CLR创建主线程执行Main()
  • 内存分配
    • :存储值类型、方法参数(自动回收)
    • 托管堆:存储引用类型对象
  • 异常处理:基于异常表实现try/catch/finally

内存分配示例:
$$ \text{对象大小} = \text{对象头} + \text{类型指针} + \sum{\text{字段大小}} $$

4. 垃圾回收(GC)阶段

当堆内存不足或系统触发时:

GC核心过程
  1. 挂起线程:暂停所有托管线程
  2. 标记阶段
    • 从GC根(静态字段、局部变量等)开始遍历
      $ \text{可达对象} = \bigcup_{\text{根}\in\text{GC Roots}}{\text{引用链}} $
  3. 压缩阶段(可选): $$ \text{新地址} = \text{基址} + \sum_{i=0}^{n-1}{\text{对象}_i\text{大小}} $$
  4. 修复指针:更新对象引用地址
  5. 恢复线程:继续程序执行
分代回收机制
对象特征 回收频率
Gen0 新创建对象 高频
Gen1 幸存Gen0的对象 中频
Gen2 长期存活对象 低频
5. 程序终止
  • 正常退出Main()执行完毕
  • 资源清理
    // Finalizer示例
    class Resource {
        ~Resource() { /* 清理非托管资源 */ }
    }
    

  • CLR卸载:释放所有托管堆内存,关闭应用程序域
完整生命周期图示

$$ \boxed{\begin{array}{c} \text{CLR加载} \ \downarrow \ \text{JIT编译} \ \downarrow \ \text{执行} \rightarrow \text{内存分配} \ \downarrow \ \text{GC触发} \begin{cases} \text{标记} \ \text{清理} \ \text{压缩} \end{cases} \ \downarrow \ \text{程序终止} \end{array}} $$

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐