6

在约瑟夫·阿尔巴哈里 (Joseph Albahari) 的《C# 5.0 in a nutshell》一书中,我发现了这一点

在此处输入图像描述 它说,只要您传递最后使用变量的代码行,它引用的对象就有资格进行垃圾回收(也就是说,如果没有其他变量持有对该对象的引用)。

然而,根据加州大学伯克利分校讲座,只要堆栈上存在对该对象的引用,就不会被垃圾回收。我的理解是,在方法返回之前,变量一直在堆栈上。这意味着它引用的任何对象在方法返回之前都是活动的。

这是书中的错误还是 java 和 .net 垃圾收集的工作方式不同?

4

4 回答 4

7

然而,根据加州大学伯克利分校的讲座,只要堆栈上存在对该对象的引用,就不会被垃圾回收。

你是对的。您缺少的是堆栈中不再存在引用。

对于在堆栈上构造对象的代码:

StringBuilder ref1 = new StringBuilder("object1");

变量ref1存储在堆栈中的某个内存位置:

                 0x403730:
Stack Pointer -> 0x40372C: pointer to ref1
                 0x403728: saved value of EBP
                 0x403724: saved value of return address
                 0x403720

现在是下一行:

StringBuilder ref2 = new StringBuilder("object2");

指向要存储的指针在ref2哪里?在堆栈上:是的。但是堆栈在哪里?当然是在同一个内存位置ref1!:

                 0x403730:
Stack Pointer -> 0x40372C: pointer to ref2
                 0x403728: saved value of EBP
                 0x403724: saved value of return address
                 0x403720

简单地将另一个值压入堆栈是很愚蠢的:

Stack Pointer -> 0x403730: pointer to ref2
                 0x40372C: pointer to ref1
                 0x403728: saved value of EBP
                 0x403724: saved value of return address
                 0x403720

这将是愚蠢的,因为ref1不再需要。

这就是为什么ref1有资格进行垃圾收集:不再有任何对它的引用。

于 2013-07-05T23:44:46.523 回答
5

书是对的。

在 .NET 中,垃圾收集器具有在代码中使用变量的位置的信息,并且一旦它未被使用,对象就有资格进行垃圾收集。

(但是,如果您在附加调试器的情况下运行代码,垃圾收集器会改变行为。它会在变量的整个范围内保留对象,而不仅仅是使用变量的位置,以便可以在调试器中调查对象。 )

于 2013-07-05T23:14:29.450 回答
4

我的理解是,在方法返回之前,变量一直在堆栈上。这意味着它引用的任何对象在方法返回之前都是活动的。

JIT 可以在最后一次使用后随时删除对象引用(“变量”),因此这不一定是正确的。

只要堆栈上存在对该对象的引用,就不会被垃圾回收

这是真的——但是当这个变量不再“存在于堆栈上”时,JIT 可能会改变,而这种方式不一定与你的代码匹配。

在 C# 5 中,这也可能会让人非常困惑,因为async在某些情况下,变量的存在时间可能比您预期的要长。

话虽如此,如果您需要保证某个对象在某个时候符合条件,则将引用该对象的变量设置为null显式允许您控制它何时显式符合条件。

于 2013-07-05T23:15:47.197 回答
2

这是从该方法生成的 CIL 代码:

.method private hidebysig static void  Main(string[] args) cil managed
    {
      .entrypoint
      // Code size       46 (0x2e)
      .maxstack  2
      .locals init ([0] class [mscorlib]System.Text.StringBuilder ref1,
               [1] class [mscorlib]System.Text.StringBuilder ref2,
               [2] class [mscorlib]System.Text.StringBuilder ref3)
      IL_0000:  nop
      IL_0001:  ldstr      "object1"
      IL_0006:  newobj     instance void [mscorlib]System.Text.StringBuilder::.ctor(string)
      IL_000b:  stloc.0     //here ref1 is stored on stack.
      IL_000c:  ldloc.0     //here it is loaded to be displayed by the console.writeline
      IL_000d:  call       void [mscorlib]System.Console::WriteLine(object)
      IL_0012:  nop        //from here on no more reference to ref1.
      IL_0013:  ldstr      "object2"
      IL_0018:  newobj     instance void [mscorlib]System.Text.StringBuilder::.ctor(string)
      IL_001d:  stloc.1
      IL_001e:  ldloc.1
      IL_001f:  stloc.2
      IL_0020:  ldloc.2
      IL_0021:  call       void [mscorlib]System.Console::WriteLine(object)
      IL_0026:  nop        //and from here on ref2 and ref3 also.
      IL_0027:  call       valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
      IL_002c:  pop
      IL_002d:  ret
    } // end of method Program::Main
于 2013-07-06T00:49:19.220 回答