65.9K
CodeProject 正在变化。 阅读更多。
Home

垃圾回收和 C#

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (26投票s)

2016 年 4 月 25 日

CPOL

5分钟阅读

viewsIcon

229118

downloadIcon

923

本文将介绍 C# 中的内存管理、垃圾回收、托管和非托管资源。

什么是垃圾回收?我们为什么需要它?

在 C# 中创建任何对象时,CLR (公共语言运行时) 会从堆中为该对象分配内存。对于每个新创建的对象,都会重复此过程。但万事皆有其限,内存并非无限,我们需要清理一些已使用的空间来为新对象腾出位置。此时,就引入了垃圾回收的概念。垃圾回收器负责内存的分配和回收。GC (垃圾回收器) 会遍历堆,收集应用程序不再使用的所有对象,然后将它们从内存中释放。

内存事实

当任何进程被触发时,系统会为该进程分配一个独立的虚拟空间,该空间来自物理内存(所有进程共享)。任何程序处理的都是虚拟空间,而不是物理内存。GC 在分配和释放内存时也处理相同的虚拟内存。基本上,虚拟内存中存在空闲块(也称为“空洞”),当任何对象请求内存分配时,管理器会搜索空闲块并为该对象分配内存。

虚拟内存有三个块

  • 空闲(空白空间)
  • 保留(已分配)
  • 已提交(此块已分配给物理内存,不可用于空间分配)

**您可能会遇到由于虚拟内存已满而导致的内存不足错误。

GC 如何工作?

GC 在托管堆上工作,托管堆是一块用于存储对象的内存块。当垃圾回收过程启动时,它会检查死对象和不再使用的对象,然后压缩活动对象的空间,并尝试释放更多内存。

基本上,堆由不同的“”管理,它存储和处理长期存活和短期存活的对象。请看下面的堆代:

  • 0 代(零):此代保存短期存活对象,例如临时对象。GC在此代中频繁启动垃圾回收过程。
  • 1 代(一):此代是短期存活对象和长期存活对象之间的缓冲区。
  • 2 代(二):此代保存长期存活对象,例如静态变量和全局变量,这些对象需要持续一段时间。在 0 代中未被回收的对象会被移至 1 代,这些对象称为幸存者。类似地,在 1 代中未被回收的对象会被移至 2 代,此后对象将保留在同一代中。

GC 如何判断对象是否活动?

GC 检查以下信息以判断对象是否活动:

  • 它收集由用户代码或 CLR 分配的所有对象句柄。
  • 跟踪 `static` 对象,因为它们被引用到其他对象。
  • 使用由堆栈遍历器和 JIT 提供的堆栈。

何时触发 GC?

GC 没有特定的触发时间,GC 会在以下情况下自动开始操作:

  1. 当虚拟内存空间不足时。
  2. 当分配的内存超过可接受的阈值时(当 GC 发现幸存率(活动对象)很高时,它会提高分配阈值)。
  3. 当我们显式调用 `GC.Collect()` 方法时。由于 GC 是持续运行的,因此我们实际上不需要调用此方法。

什么是托管和非托管对象/资源?

**VS**

简单来说:

托管对象由 CLR 创建、管理并受其范围控制,是运行时管理的纯 .NET 代码。任何位于 .NET 范围和 .NET 框架类内的内容,例如 `string`、`int`、`bool` 变量,都被称为托管代码。

非托管对象在 .NET 库控制之外创建,并且不受 CLR 管理。此类非托管代码的示例是 COM 对象、文件流、连接对象、互操作对象。(基本上,是在 .NET 代码中引用的第三方库。)

清理非托管资源

当我们创建非托管对象时,GC 无法清除它们,我们需要在使用完它们后显式释放这些对象。大多数非托管对象都封装/隐藏在操作系统资源周围,例如文件流、数据库连接、网络相关实例、不同类的句柄、注册表、指针等。GC 负责跟踪所有托管和非托管对象生命周期,但 GC 仍然不知道如何释放非托管资源。

有几种方法可以清理非托管资源:

  • 实现 `IDisposable` 接口和 `Dispose` 方法。
  • “`using`”块也用于清理非托管资源。

有几种方法可以实现 `Dispose` 方法:

  • 使用“`SafeHandle`”类实现 `Dispose`(这是一个内置的 `abstract` 类,实现了“`CriticalFinalizerObject`”和“`IDisposable`”接口)。
  • 重写 `Object.Finalize` 方法(此方法在对象被销毁前清理该对象使用的非托管资源)。

下面我们来看一个清理非托管资源的示例代码:

  • 使用“`SafeHandle`”类实现 `Dispose`。
    class clsDispose_safe
        {
            // Take a flag to check if object is already disposed
            bool bDisposed = false;
    
            // Create a object of SafeHandle class
            SafeHandle objSafeHandle = new SafeFileHandle(IntPtr.Zero, true);
    
            // Dispose method (public)
            public void Dispose1()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
    
            // Dispose method (protected)
            protected virtual void Dispose(bool bDispose)
            {
                if (bDisposed)
                    return;
    
                if (bDispose)
                {
                    objSafeHandle.Dispose();
                    // Free any other managed objects here.
                }
    
                // Free any unmanaged objects here.
                //
                bDisposed = true;
            }
        }
  • 通过重写“`Object.Finalize`”方法实现 `Dispose`。
    class clsDispose_Fin
        {
            // Flag: Has Dispose already been called?
            bool disposed = false;
    
            // Public implementation of Dispose pattern callable by consumers.
            public void Dispose()
            {
                Dispose1(true);
                GC.SuppressFinalize(this);
            }
    
            // Protected implementation of Dispose pattern.
            protected virtual void Dispose1(bool disposing)
            {
                if (disposed)
                    return;
    
                if (disposing)
                {
                    // Free any other managed objects here.
                    //
                }
    
                // Free any unmanaged objects here.
                //
                disposed = true;
            }
    
            ~clsDispose_Fin()
            {
                Dispose1(false);
            }
        }

“using”语句

`using` 语句可确保对象被释放,简而言之,它提供了一种便捷的方式来使用 `IDisposable` 对象。当一个 `Object` 离开范围时,`Dispose` 方法将自动被调用。本质上,`using` 块的作用与“`TRY`...`FINALLY`”块相同。为了演示,创建一个实现 `IDisposable` 的类(它应该有一个 `Dispose()` 方法)。“`using`”语句即使在发生异常时也会调用“`dispose`”方法。

请看下面的代码片段:

class testClass : IDisposable
{
    public void Dispose()
    {
        // Dispose objects here
 	// clean resources
 	Console.WriteLine(0);
    }
}

//call class
class Program
{
    static void Main()
    {
        // Use using statement with class that implements Dispose.
 	using (testClass objClass = new testClass())
 	{
     		Console.WriteLine(1);
	}
	 Console.WriteLine(2);
    }
}

//output
1
0
2

//it is same as below TRY...Finally code
{
    clsDispose_Fin objClass = new clsDispose_Fin();
    try
    {
        //code goes here 
    }
    finally
    {
        if (objClass != null)
        ((IDisposable)objClass).Dispose();
    }
}

在上面的示例中,打印 1 后,`using` 块结束,调用 `Dispose` 方法,然后调用 `using` 语句之后的语句。

回顾

  • 垃圾回收器管理内存的分配和回收。
  • GC 在托管堆上工作,托管堆是一块用于存储对象的内存块。
  • GC 没有特定的触发时间,GC 会自动开始操作。
  • 托管对象由 CLR 创建、管理并受其范围控制。
  • 非托管对象封装了操作系统资源,例如文件流、数据库连接、网络相关实例、不同类的句柄、注册表、指针等。
  • 可以使用“`Dispose`”方法和“`using`”语句来清理非托管资源。

谢谢

此主题还有更多内容,我将在后续版本中介绍。欢迎提出建议和疑问。

感谢阅读!

© . All rights reserved.