当前位置: 首页 > article >正文

基于C#了解垃圾回收机制

1. 什么是垃圾回收(Garbage Collection,简称GC)

在C#等支持自动内存管理的编程语言中,垃圾回收机制是一种自动释放不再被程序使用的内存空间的机制。

程序运行过程中会不断创建对象实例,这些对象占用内存空间,而当某些对象不再被需要时(比如其对应的引用变量不再指向它,且程序后续也不会再访问到它),就需要回收它们所占用的内存,以便内存能被高效地重复利用,避免内存泄漏等问题。

图片

2. 垃圾回收的工作原理

C#的垃圾回收器基于引用计数和跟踪算法来实现内存管理。当一个对象没有任何引用指向它时,垃圾回收器就会认为这个对象是不再需要的,从而将其占用的内存回收。

C#的垃圾回收器主要包括三个代(Generation):第0代、第1代和第2代。不同代的对象具有不同的生命周期和回收频率。第0代包含新创建的对象,第1代包含从第0代晋升的对象,第2代包含从第1代晋升的对象以及长期存活的对象。

3. 垃圾回收的工作流程

  • 标记阶段:
  • 垃圾回收器会从一组称为“根”(Roots)的对象开始遍历,这些“根”通常包括全局变量、静态变量、当前正在执行的方法的局部变量(在栈上的引用)等。从这些“根”出发,沿着对象引用关系链进行遍历,能被访问到的对象会被标记为“可达”(Reachable),而那些无法通过“根”对象引用链访问到的对象就被认为是“不可达”(Unreachable),也就是可以被回收的垃圾对象。例,在一个方法中创建了一个局部对象,方法执行结束后,如果没有其他地方再引用这个对象,那么从“根”出发就无法访问到它了,它就会在这个阶段被标记为不可达。
  • 清除阶段:
  • 在标记出所有的不可达对象后,垃圾回收器会回收这些对象所占用的内存空间,使这些内存可以重新被分配给新创建的对象使用。不同的垃圾回收算法在具体的回收内存方式上会有所不同,比如有的是直接释放内存,有的可能会进行内存整理(将存活的对象移动到一起,便于后续内存分配更加连续高效等操作)。

4. 常见的垃圾回收算法

  • 标记-清除算法(Mark-Sweep):
  • 这是最基础的一种算法,先标记出所有需要回收的对象(不可达对象),然后统一回收它们占用的内存空间。缺点是会产生内存碎片,因为回收后的内存空间可能是不连续的小块,不利于后续分配较大对象时的内存申请。
  • 标记-整理算法(Mark-Compact):
  • 在标记阶段类似标记-清除算法,标记出不可达对象后,会将所有存活的对象(可达对象)向一端移动,然后直接清理掉边界之外的内存空间,这样可以避免内存碎片问题,但移动对象会有一定的性能开销。

       图片                   

                                            主流的"标记并清除"的方式

  • 复制算法(Copying):
  • 它将可用内存划分为两块大小相等的区域,每次只使用其中一块。当进行垃圾回收时,把存活的对象从正在使用的这块区域复制到另一块空闲区域,然后直接把原来使用的区域全部清理掉。优点是简单高效且不会产生内存碎片,缺点是可用内存空间利用率不高,始终只能使用一半的内存。
  • 引用计数算法 :
  • 每个对象都有一个引用计数器,当有新的引用指向对象时,计数器增加;当引用离开作用域或被释放时,计数器减少。当计数器为0时,对象被视为不再需要,可以被回收。

图片

代码示例来观察垃圾回收的效果(简单示意)

以下是一个简单的C#代码示例,通过创建一些对象并让它们的引用关系变化,来观察垃圾回收的情况(实际中很难精确控制垃圾回收器何时执行,这里只是模拟一种可能触发垃圾回收的场景)。

using System;

class MyClass
{
    ~MyClass()
    {
        Console.WriteLine("对象被回收了");
    }
}


class Program
{
    static void Main()
    {
        // 创建一个对象实例
      	MyClass obj1 = new MyClass();
        // 再创建一个对象实例
      	MyClass obj2 = new MyClass();
        // 让obj2不再引用它原来指向的对象,模拟失去引用的情况
        obj2 = null;

        // 强制进行垃圾回收(虽然不能保证立刻执行,但会提示垃圾回收器可以尝试回收了)
        GC.Collect();
        GC.WaitForPendingFinalizers();


        // 让obj1也不再引用对象,再次模拟失去引用情况
        obj1 = null;
        // 再次强制进行垃圾回收
        GC.Collect();
        GC.WaitForPendingFinalizers();

        Console.ReadKey();
    }
}

输出结果:

对象被回收了!
对象被回收了!

在上述代码中:

  • 首先定义了一个 MyClass 类,并且重写了析构函数(~MyClass()),在析构函数中输出一条表示对象被回收的信息,这样当对象被垃圾回收时就能看到相应提示。
  • 在 Main 方法中,先创建了 obj1 和 obj2 两个 MyClass 对象实例,然后将 obj2 赋值为 null,此时原来 obj2 指向的对象就失去了一个引用,通过调用GC.Collect()(主动发起垃圾回收)和GC.WaitForPendingFinalizers()(等待终结器执行完毕,析构函数在这里属于终结器相关操作)来尝试触发垃圾回收,看看是否会回收那个失去引用的对象(如果符合回收条件的话)。之后又将 obj1 也赋值为 null,再次进行垃圾回收相关操作,观察对象被回收的情况。

图片                                                   代码思想也是来源于生活

需要注意的是,在实际的生产代码中,一般很少会主动去频繁调用 GC.Collect(),因为垃圾回收器本身有自己的一套优化策略和触发机制来决定何时进行垃圾回收最合适,过度手动干预可能反而会影响程序的性能和正常运行逻辑。

另外,不同版本的.NET框架以及运行时环境下的垃圾回收机制在细节和实现上可能会有一定差异,本文只是介绍了基本的通用原理和简单的示例演示。

图片

图片


http://www.kler.cn/a/456228.html

相关文章:

  • Redis 实战篇 ——《黑马点评》(上)
  • 源码分析之Openlayers中MultiPolygon类
  • 功能测试和接口测试
  • oracle怎样使用logmnr恢复误删除的数据
  • 工作编码案例--UDP多播 和 本地套接字bind
  • 深入理解Java虚拟机(JVM)
  • Faster R-CNN
  • 【intellij idea 创建springBoot 搭配mybatis oracle】
  • C# 简单使用NModbus
  • 5.系统学习-PyTorch与多层感知机
  • JavaFX FXML模式下的布局
  • 尚硅谷Vue3入门到实战 —— 01 创建 VUe3工程
  • unity中Timeline动画的播放和播放中如何判断播放结束
  • springboot整合log4j2日志框架1
  • 25秋招面试总结
  • Kotlin快速入门
  • 惠州市政数局局长杨伟斌:惠州市公共数据授权运营模式探索
  • 雷池 WAF 搭配阿里云 CDN 使用教程
  • javaEE-多线程案例-单例模式
  • 深度学习:从原理到搭建基础模型
  • RabbitMQ HAProxy 负载均衡
  • 应对TensorFlow导入Keras时发生的错误问题
  • SPI实验 LED数码管
  • 用三种安全思维重新审视零信任网络
  • 云效流水线自动化部署web静态网站
  • opencl 封装简单api