GC运行后对象会发生什么

本文关键字:什么 对象 运行 GC | 更新日期: 2023-09-27 18:19:40

考虑以下语句

void foo()
{
    string text = "something";
    int a = 10;
    int b = a;
    var array = new int[5]{2,1,3,5,4};
    GC.Collect();
}

**Stack**           **Heap**
text = 0x20         0x20 something
a = 10
b = 10
array = 0x50:0x55   0x50 2
                    0x52 1
                    0x53 3
                    0x54 5
                    0x55 4

一些假设:

  1. 在方法的末尾调用GC.Collect()
  2. GC运行(假设)

问题是:

  1. 堆中的值是多少?

    我回答了内存位置0x20、0x50-0x55将被回收并删除。他再次问道,内存位置有什么值?我猜可能是垃圾值。他说的不对。(WTx?)

  2. 堆栈中的项目会发生什么情况?文本、a、b和数组包含哪些值?

    我回答说,因为它们包含的引用已经被删除,它们也将被回收。他问这些变量现在的值是多少。

  3. GC Collection之后,重新绘制上面的表格列。

    GC运行后

    **Stack**           **Heap**
    text (cleared)      0x20 Garbage/NULL
    a (cleared) 
    b (cleared) 
    array (cleared)     0x50 Garbage/NULL
                        0x52 Garbage/NULL
                        0x53 Garbage/NULL
                        0x54 Garbage/NULL
                        0x55 Garbage/NULL
    

结果:他说答案不正确。你知道我在这里错过了什么吗?

GC运行后对象会发生什么

对此不可能给出确定性的答案,但可以对行为进行一些思考。

首先,所有的变量都是局部变量。因此,方法返回之后,这些变量就超出了作用域。他们现在居住的内存(或CPU寄存器)中的确切位置包含未知内容。其他的东西现在可能生活在那个记忆区,我们不知道我们不知道。因此,他们被清除了吗?谁知道呢。

然而,出于所有意图和目的,在方法返回之后,这些变量就不再存在

如果您认为"这些变量存在于堆栈中",请注意堆栈是一个实现细节。

现在,您还分配了两个堆对象,一个字符串和一个数组。

由于这些仅由局部变量引用,而在方法返回后局部变量超出了范围,因此这些变量不再被视为活动变量,并且有资格进行收集。

然而,这里的"收藏"是什么意思?这是否意味着垃圾收集器会清除内存区域?一点也不。

相反,当垃圾收集内存时,它实际上会做相反的事情,它收集有生命的对象。这些对象在内存中被压缩并四处移动,该字符串和数组所在的区域可以被其他对象覆盖。可能是一个只包含零的数组,在这种情况下,内存区域可能看起来"已清除",但可能不会。

但是等一下,这个字符串发生了一些特殊的事情。由于这个字符串是作为文字写在源代码中的,所以当程序启动时,它实际上是内部的。因此,它不是收集的,它将仍然生活在记忆中的某个地方。换言之,这根绳子仍然活着,在记忆中的某个地方。可能是变量引用它时它所在的位置,也可能是其他地方我们不知道

但是,当垃圾收集器压缩活动对象时,可以覆盖组成数组对象的字节。

好的,所有这些都发生在方法返回后

如果GC.Collect()在方法返回之前运行垃圾收集循环,会发生什么?

好吧,如果你在RELEASE版本中,上面的一切仍然有效。如果方法中不再使用变量(即,您已经在执行时间线中传递了最后一次使用),那么方法中有一个引用堆上某个对象的局部变量这一事实毫无意义。

但是,如果您在DEBUG构建中,或者附加了调试器,则所有局部变量的作用域都会延长到方法的末尾。因此,它们仍然被视为活动引用,并将使堆上的对象保持活动状态。在这种情况下,将不会收集数组。

但是,任何时候垃圾收集运行时,堆对象在内存中的地址都可能发生变化,因为这些对象是与其他对象一起压缩或升级到不同的一代。


以下是您问题的实际答案:

以上大部分内容都是实现细节。我们不应该关心的事情,因为它可能会因为优化而改变。

我们可以推理很多事情,但我们不应该推理实际底层内存是如何处理的。

你在评论中说,你以为自己在接受编译作家的采访,我认为这种感觉是正确的。如果你要为微软的JIT或内存管理团队面试,至少会在工作中教授一些关于这些东西如何工作的知识,但如果你已经知道的话,这可能是一个额外的收获。

然而,对于一个普通的.NET程序员来说,这是不需要关心的。其他聪明人关心这一点。

对象以前占用的内存可能发生三种情况之一:

  • 它被另一个对象覆盖。GC压缩堆时移动的一个。这是最有可能的结果
  • 它被添加到堆段的空闲列表中,准备供下一次分配使用。通常是在将其与可用空间或块前后的分配合并之后(如果它们也被释放),这是常见的
  • 这是堆段中剩下的最后一个分配。该段可能被添加到未使用的段池中,当需要新的第0代段时,这些段将被重新使用。或者,它的地址空间可能会返回到操作系统

大对象堆有点不同,第一个项目符号不适用。请注意,"记忆位置被删除"的心理模型不是很准确。您将在"内存位置将被重新使用"方面遥遥领先。堆栈也是如此,没有活动的"删除堆栈帧"代码。它只是被遗忘了,迟早会被覆盖。几乎总是更快,微秒。

这是您无法回答的问题,因为它是执行平台的实现细节。根据平台/版本的不同,答案可能完全不同。您可以考虑的是方法范围、值类型与引用类型以及字符串的特殊性。