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 在机器代码中不再有用?
这取决于a
在fun
中的使用方式。GC 能够确定是否有任何给定的对象已植根。在这种情况下,a
是A
实例中字段m_a
的别名,并且该对象被视为具有 root 权限。但是,如果 JIT 编译器确定a
在fun
的其余部分中未使用,则从该方法的该点开始,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 的新实例将超出范围,因此需要进行垃圾回收。