当前位置: 首页 > 知识库问答 >
问题:

了解.NET中的垃圾收集

栾峰
2023-03-14

请考虑以下代码:

public class Class1
{
    public static int c;
    ~Class1()
    {
        c++;
    }
}

public class Class2
{
    public static void Main()
    {
        {
            var c1=new Class1();
            //c1=null; // If this line is not commented out, at the Console.WriteLine call, it prints 1.
        }
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine(Class1.c); // prints 0
        Console.Read();
    }
}

现在,即使main方法中的变量C1超出了作用域,并且在调用gc.collection()时没有被任何其他对象进一步引用,为什么它没有在那里完成呢?

共有1个答案

公孙盛
2023-03-14

您在这里被绊倒了,并且得出了非常错误的结论,因为您使用的是调试器。您需要以在用户机器上运行的方式运行代码。使用build+Configuration manager切换到Release build,将左上角的“Active solution Configuration”组合框更改为“Release”。接下来,进入Tools+Options,Debugging,General并取消勾选“Suppress JIT Optimization”选项。

现在再次运行您的程序并修补源代码。注意额外的大括号是如何完全没有作用的。并注意将变量设置为null完全没有区别。它将始终打印“1”。它现在按照你所希望和期望的方式工作。

这就需要解释为什么在运行调试构建时它的工作方式如此不同。这需要解释垃圾收集器是如何发现局部变量的,以及调试器的存在对此有何影响。

这个表对于垃圾收集器是必不可少的,它需要知道在执行收集时在哪里查找对象引用。当引用是GC堆上对象的一部分时,这是非常容易的。当对象引用存储在CPU寄存器中时,肯定不容易做到。桌子上写着去哪里看。

表中“不再使用”的地址非常重要。它使垃圾收集器非常高效。它可以收集一个对象引用,即使它是在一个方法内部使用的,而且那个方法还没有完成执行。这是非常常见的,例如,Main()方法只会在程序终止之前停止执行。很明显,您不希望在Main()方法中使用的任何对象引用在程序的持续时间内存在,这将导致泄漏。jitter可以使用表来发现这样的局部变量不再有用,这取决于程序在进行调用之前在Main()方法中进行了多远。

与该表相关的一个几乎神奇的方法是gc.keepalive()。这是一个非常特殊的方法,它根本不生成任何代码。它的唯一职责是修改该表。它延长了局部变量的生存期,防止它存储的引用被垃圾收集。您需要使用它的唯一时间是阻止GC过于急切地收集引用,这种情况可能发生在将引用传递给非托管代码的互操作场景中。垃圾回收器无法看到此类代码正在使用此类引用,因为它不是由jitter编译的,因此没有指定在何处查找引用的表。将委托对象传递给非托管函数(如EnumWindows())是当您需要使用gc.keepalive()时的样板示例。

因此,正如您在发布版本中运行样例片段后所看到的那样,局部变量可以在方法完成执行之前提前被收集。更强大的是,如果一个对象的一个方法不再引用它,那么它可以在它的一个方法运行时被收集。那有一个问题,调试这样的方法是非常尴尬的。因为您可以将变量放在“监视”窗口中或对其进行检查。并且如果发生GC,它将在调试时消失。这将是非常不愉快的,所以抖动知道有一个调试器附加。然后修改表并更改“最后使用的”地址。并将其从正常值更改为方法中最后一条指令的地址。只要方法没有返回,变量就保持活动状态。这使您可以一直监视它直到方法返回。

这现在也解释了你前面看到的和为什么问这个问题。它打印“0”,因为GC.collect调用无法收集引用。该表指出,该变量在gc.collect()调用之后一直使用到方法的末尾。通过附加调试器和运行调试生成而被迫这样说。

变量设置为null现在确实有效果,因为GC将检查变量,不再看到引用。但请确保您不会落入许多C#程序员所落入的陷阱,实际上编写那些代码是毫无意义的。在发行版构建中运行代码时,无论该语句是否存在,都没有任何区别。事实上,抖动优化器将删除该语句,因为它没有任何作用。所以千万不要这样编写代码,即使它看起来有效果。

 类似资料:
  • 由于ElasticSearch是基于Java语言的应用,所以它必须运行在Java虚拟机上。任何Java程序都被编译成字节码,然后才能运行在JVM上。用最常规的方式思考,可以想象JVM只是执行其它的程序,并且控制程序的行为。但是除非你是在为ElasticSearch开发新的插件(这部分的内容将在第9章 开发ElasticSearch插件中论述),否则这不是你关注的重点。你需要关注的重点是垃圾收集器,

  • 主要内容:垃圾收集的优势,垃圾收集的条件,阶段过程在本章中,我们将介绍垃圾收集的概念,垃圾收集是.NET托管代码平台最重要的特性之一。 垃圾收集器(GC)管理内存的分配和释放。 垃圾收集器用作自动内存管理器。 我们不需要知道如何分配和释放内存或管理使用该内存的对象的生命周期 每当使用关键字声明对象或将值类型装箱时,都会进行分配。分配通常非常快。 当没有足够的内存分配一个对象时,GC必须收集和处理垃圾内存以使内存可用于新的分配。 这个过程被称为垃圾

  • 问题:那么扫一扫实际上是什么意思?它是实际的垃圾回收(回收无法访问的对象并释放内存?)?还是意味着什么不同? 如果是这样,如果我们省略了扫描阶段,我们会遇到什么样的麻烦?

  • 一、垃圾收集算法 1.标记-清除算法 最基础的收集算法是“标记-清除”(Mark-Sweep)算法,如同它的名字一样,算法分为“标记”和“清除”两个阶段。 ①首先标记出所有需要回收的对象 ②在标记完成后统一回收所有被标记的对象。 不足: 效率问题:标记和清除两个过程的效率都不高 空间问题:标记清除之后产生大量不连续的内存碎片,空间碎片太多可能会导致以后程序运行过程中需要分配较大对象时,无法找到足够

  • Kubernetes 垃圾收集器的角色是删除指定的对象,这些对象曾经有但以后不再拥有 Owner 了。 注意:垃圾收集是 beta 特性,在 Kubernetes 1.4 及以上版本默认启用。 Owner 和 Dependent 一些 Kubernetes 对象是其它一些的 Owner。例如,一个 ReplicaSet 是一组 Pod 的 Owner。具有 Owner 的对象被称为是 Owner

  • 垃圾回收 我们对生产中花了很多时间来调整垃圾回收。垃圾回收的关注点与Java大致相似,尽管一些惯用的Scala代码比起惯用的Java代码会容易产生更多(短暂的)垃圾——函数式风格的副产品。Hotspot的分代垃圾收集通常使这不成问题,因为短暂的(short-lived)垃圾在大多情形下会被有效的释放掉。 在谈GC调优话题前,先看看这个Attila的报告,它阐述了我们在GC方面的一些经验。 Scal