Administrator
发布于 2025-10-15 / 6 阅读
0
0

对象声明周期

在 C# 中,“类的对象的生命周期”是指从对象被创建(分配内存、初始化)到它被垃圾回收器(GC)回收、内存被释放的整个过程。理解这个过程,对写出高性能、无内存泄漏的程序非常关键。


✅ 一句话总结:

对象的生命周期 = 创建 → 使用 → 不再被引用 → GC 标记 → 回收 → 内存释放


🔍 分阶段详解:

阶段

描述

关键点

1. 创建

使用 new 关键字创建对象

分配在托管堆(Managed Heap)上,调用构造函数

2. 使用

对象被程序引用并使用

只要对象仍被根(GC Roots)引用,就不会被回收

3. 不可达

没有引用指向该对象

例如:局部变量超出作用域、引用被设为 null

4. GC 标记

GC 在回收前标记所有可达对象

使用“标记-清除”算法

5. 回收

不可达对象被回收

可能调用 终结器(Finalizer)(如果有)

6. 内存释放

内存被压缩并返还给堆

不是立即发生,由 GC 决定


🧠 关键概念补充:

✅ GC Roots 包括:

  • 全局静态变量

  • 当前线程栈上的局部变量

  • CPU 寄存器中的引用

  • 正在使用的 GCHandle

⚠️ 注意:

  • GC 是非确定的:你无法预测对象什么时候被回收。

  • Finalizer 不保证及时执行:不要依赖它来释放关键资源。

  • 推荐使用 Dispose() 模式(即 IDisposable)来释放非托管资源(如文件句柄、数据库连接)。


🧪 示例代码:

class MyClass : IDisposable
{
    public MyClass()
    {
        Console.WriteLine("对象创建");
    }
​
    ~MyClass() // 终结器(不推荐依赖)
    {
        Console.WriteLine("终结器被调用");
    }
​
    public void Dispose()
    {
        Console.WriteLine("Dispose 被调用");
        GC.SuppressFinalize(this); // 告诉 GC 不用再调用终结器
    }
}
​
class Program
{
    static void Main()
    {
        MyClass obj = new MyClass();
        obj.Dispose(); // 手动释放资源
        obj = null; // 取消引用
​
        GC.Collect(); // 强制触发 GC(仅用于测试)
        GC.WaitForPendingFinalizers();
​
        Console.WriteLine("程序结束");
    }
}

✅ 总结一句话(面试可用):

C# 中对象的生命周期由 CLR 管理,从 new 创建开始,到不再被任何根引用后,由 GC 在某个不确定时间点回收内存;非托管资源应通过 IDisposable 显式释放。


如果你有具体场景(比如:内存泄漏、Dispose 没调用、GC 不回收等),我可以帮你分析。

下面给出一段最小可运行的 C# 控制台程序,把“对象的一生”全部打印出来。 代码里每行都加了中文标注,运行后屏幕上的输出就是生命周期的时间线

using System;
​
namespace LifeCycleDemo
{
    // 1. 定义一个“会说话”的类,方便我们观察每一步
    class DemoObject : IDisposable
    {
        private readonly string _id;
​
        // 构造阶段:对象诞生
        public DemoObject(string id)
        {
            _id = id;
            Console.WriteLine($"【构造】   {_id} 对象在托管堆上分配,构造函数执行完毕。");
        }
​
        // 模拟“干活”
        public void DoWork()
        {
            Console.WriteLine($"【使用】   {_id} 正在干活,this={GetHashCode()}");
        }
​
        // 显式释放资源(IDisposable 接口)
        public void Dispose()
        {
            Console.WriteLine($"【Dispose】{_id} 被显式释放,非托管资源已清理。");
            GC.SuppressFinalize(this); // 告诉 GC“别再跑终结器”
        }
​
        // 终结器(Finalizer)—— 备胎,万一忘记 Dispose
        ~DemoObject()
        {
            Console.WriteLine($"【终结器】 {_id} 被终结,对象即将被回收。");
        }
    }
​
    class Program
    {
        static void Main()
        {
            Console.WriteLine("=== 对象生命周期演示开始 ===\n");
​
            // 2. 创建:new 导致在托管堆分配
            DemoObject obj = new DemoObject("A1");
            obj.DoWork();          // 3. 使用
​
            // 4. 主动 Dispose,释放非托管资源
            obj.Dispose();
​
            // 5. 断开引用:对象变成“不可达”
            obj = null;
​
            // 6. 强制 GC(仅演示用,生产代码不要这么写)
            Console.WriteLine("\n【GC】     强制触发垃圾回收...");
            GC.Collect(0, GCCollectionMode.Forced, blocking: true, compacting: true);
            GC.WaitForPendingFinalizers(); // 等终结器跑完
​
            Console.WriteLine("\n=== 对象生命周期演示结束 ===");
        }
    }
}

把文件保存为 LifeCycleDemo.cs,编译运行:

dotnet run

典型输出(已对齐):

=== 对象生命周期演示开始 ===
​
【构造】   A1 对象在托管堆上分配,构造函数执行完毕。
【使用】   A1 正在干活,this=54267266
【Dispose】A1 被显式释放,非托管资源已清理。
​
【GC】     强制触发垃圾回收...
=== 对象生命周期演示结束 ===

注意: 由于我们在 Dispose() 里调用了 GC.SuppressFinalize(this)终结器不会执行,所以看不到“【终结器】”这一行。 如果把 Dispose() 那行注释掉再运行,就能观察到“【终结器】”输出,证明对象真的被 GC 回收了。

这样,“创建 → 使用 → 显式释放 → 失去引用 → GC 回收” 整个生命周期就完整跑通并打印出来了。


评论