"ref int m_a"如何防止包含m_a的对象被 GC 回收?


class A
{
   public int m_a;
}
void fun(ref int a)
{
   ...
}
fun(ref new A().m_a);

在乐趣中,"ref int a"如何防止对象(新A())在从乐趣中返回之前被回收?

<example 0>
using System;
class A
{
    public int m_a;
    ~A()
    {
        Console.WriteLine("~A()");
    }
}
class Program
{
    static void fun(ref int a)
    {
        Console.WriteLine("Begin<<<<<<<<<<<<<<");
        a++;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("End>>>>>>>>>>>>>>>>>");
    }
    static void Main(string[] args)
    {
        fun(ref new A().m_a);
        Console.WriteLine("Over");
    }
}

output:
Begin<<<<<<<<<<<<<<
~A()
End>>>>>>>>>>>>>>>>>
Over
<example 1>
using System;
class A
{
    public int m_a;
    ~A()
    {
        Console.WriteLine("~A()");
    }
}
class Program
{
    static void fun(ref int a)
    {
        Console.WriteLine("Begin<<<<<<<<<<<<<<");
        a++;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        //add a code
        a++;
        Console.WriteLine("End>>>>>>>>>>>>>>>>>");
    }
    static void Main(string[] args)
    {
        fun(ref new A().m_a);
        Console.WriteLine("Over");
    }
}
output:
Begin<<<<<<<<<<<<<<
End>>>>>>>>>>>>>>>>>
Over
~A()

请在VS中通过发布模式构建。我查看ASM代码,只添加了两行:

             a++;
0000002f  mov         eax,dword ptr [ebp-4] 
00000032  inc         dword ptr [eax] 

两个示例之间的其他部分是相同的。GC 如何确保变量 a 在机器代码中不再有用?

这取决于afun中的使用方式。GC 能够确定是否有任何给定的对象已植根。在这种情况下,aA实例中字段m_a的别名,并且该对象被视为具有 root 权限。但是,如果 JIT 编译器确定afun的其余部分中未使用,则从该方法的该点开始,A 的实例将不再具有根权限,并且符合垃圾回收的条件。

一些例子:

void fun(ref int a)
{
   // forgot to use a. our object is already eligible for GC!
   for(int i = 0; i < 10; i++)
   {
      Console.WriteLine(i);
   }
}
void fun2(ref int a)
{       
   for(int i = 0; i < 10; i++)
   {
      Console.WriteLine(a);
   }
   // GC has to wait until a is no longer in use. now our object is eligible for GC.
}
void fun3(ref int a)
{
   // can't gc yet. a is still being used.
   int b = a;
   // b has a copy of the value in a so now our object is eligible for GC.
   for(int i = 0; i < 10; i++)
   {
      Console.WriteLine(b);
   }
}

更新:

我不是如何在 clr 中实现这一点的专家,但我的理解是使用 ref 会导致指向字段的托管指针m_a传递到fun 中。当 GC 运行时,根是通过对静态堆对象的引用、所有线程的调用堆栈和寄存器来确定的。我在这里猜测,但也许指向字段的托管指针m_a存储对容器对象的引用。或者,GC 可以确定给定的托管指针位于哪个对象中。无论采用哪种方式,对象都标记为从该托管引用获得 root 权限。

我认为这个旧的演示文稿(幻灯片 30 以后)就足够了,但它在评论部分发展成一些来回,所以我想我会给出一个答案。

每当 JIT 准备任何方法时,它也会构造一个"表",该表映射在方法中的任何特定点上哪些局部变量槽是"活动"的。因此,当 GC 检查每个线程时,它会获取该线程的指令指针,查阅表,并使用它来确定当前方法中的实时引用。

对于必须通知 GC 的任何内容,特定方法的机器代码中都没有写入任何内容 - JIT 分析涵盖了代码中的所有路径,并且每个方法只需执行一次。

在调试下,JIT 将所有变量标记为用于方法的整个主体 - 这使引用的存活时间超过绝对必要的时间,但确实意味着您可以在变量上次在方法中使用后检查变量的状态(例如,通过局部变量或自动窗口,或任何其他您可能突然希望引用变量的方式)

您必须事先创建 A 实例并保留对它的引用,如下所示:

    A a = new A();
    fun(ref a.m_a);

否则,当乐趣恢复时,A 的新实例将超出范围,因此需要进行垃圾回收。

最新更新