C# 垃圾收集-通过强引用访问对象是否总是安全的?

C# 垃圾收集-通过强引用访问对象是否总是安全的?,c#,garbage-collection,C#,Garbage Collection,我的理解是,当GC发现一个子图中的对象不再可以从主图中访问(通过强引用)时,它将收集它们并回收内存。我的问题是关于不可访问对象的删除顺序。这是原子操作吗?所有不可访问的对象是一次性完成的,还是GC在应用程序仍在执行时逐个完成每个对象?如果对象一个接一个地完成,是否遵循特定的顺序 如果有一个对象A包含对对象B的弱引用,那么很明显,A必须在调用任何B的实例方法之前检查B是否仍然存在。现在假设B持有对另一个对象C的强引用。如果B仍然活着,我是否总是保证C也仍然活着?GC是否有可能将B&C标记为收集,但

我的理解是,当GC发现一个子图中的对象不再可以从主图中访问(通过强引用)时,它将收集它们并回收内存。我的问题是关于不可访问对象的删除顺序。这是原子操作吗?所有不可访问的对象是一次性完成的,还是GC在应用程序仍在执行时逐个完成每个对象?如果对象一个接一个地完成,是否遵循特定的顺序

如果有一个对象A包含对对象B的弱引用,那么很明显,A必须在调用任何B的实例方法之前检查B是否仍然存在。现在假设B持有对另一个对象C的强引用。如果B仍然活着,我是否总是保证C也仍然活着?GC是否有可能将B&C标记为收集,但C在B之前完成

我的猜测是,从B访问C总是安全的(因为这是一个强引用)。但我想确认这个假设,因为如果我错了,我可能会引入一个非常间歇性的难以追踪的bug

public class ClassA
{
    private readonly WeakReference objBWeakRef;

    public ClassA(ClassB objB)
    {
        objBWeakRef = new WeakReference(objB);
    }

    public void DoSomething()
    {
        // The null check is required because objB 
        // may have been cleaned-up by the GC
        var objBStrongRef = (ClassB) objBWeakRef.Target;
        if (objBStrongRef != null)
        {
            objBStrongRef.DoSomething();
        }
    }
}

public class ClassB
{
    private readonly ClassC objCStrongRef;

    public ClassB(ClassC objC)
    {
        objCStrongRef = objC;
    }

    public void DoSomething()
    {
        // Do I also need to do some kind of checking here?
        // Is it possible that objC has been collected, but 
        // the GC has not yet gotten around to collecting objB?
        objCStrongRef.DoSomething();
    }
}

public class ClassC
{
    public void DoSomething()
    {
        // do something here... if object is still alive.
    }
}
从杰弗里·里克特的“CLR通过C#”中:

此外,要意识到这样一个事实,即你无法控制何时开始 将执行Finalize方法。Finalize方法在垃圾回收时运行 收集发生,这可能在应用程序请求时发生 更多的记忆。此外,CLR没有对订单做出任何保证 在其中调用Finalize方法,因此应该避免编写 Finalize方法,该方法访问其类型定义 确定方法;这些其他目标本可以最终确定 已经。但是,访问值类型实例或 未定义Finalize方法的引用类型对象。你也 调用静态方法时需要小心,因为这些方法 可以在内部访问已完成的对象,从而导致 静态方法的行为变得不可预测


在正常的托管方法中,只要B是活动的,就可以依赖于B引用的具有强引用的所有内容。B中对C的引用在B的使用寿命内是有效的

异常出现在终结代码中。如果您实现了一个终结器(这应该被认为是一个不寻常的情况,而不是正常情况),那么终结器的调用链中的所有赌注都是无效的。在GC注意到对象不再被活动对象引用后,终结器可能不会执行很长时间——带有终结器的对象往往比“普通”对象挂起的时间长得多,这是不使用终结器的另一个原因。在极端紧急关闭的情况下,终结器可能根本不会执行。您不能假设终结器将在哪个线程上执行,或者终结器将以什么顺序执行。您应该避免引用对象中的任何引用变量,因为您不知道它们是否已经完成


但这都是学术性的。您的代码示例没有实现终结器,因此您不必担心Bizzaro终结器世界。

是的,如果您的
ClassB
通过
objCStrongRef
引用了
ClassC
实例,然后,如果
ClassB
仍然存在,您就不必担心
ClassC
会随机蒸发。例外情况是,如果您编写终结器,即

~ClassB() { ...}
在那里,根本不应该尝试与
objCStrongRef
交谈;因为你不知道哪一个对象先完成。如果
ClassC
在终结时需要一些东西,那么它应该有一个单独的
~ClassC()
——尽管在现实中终结器方法非常罕见,并且您不应该在没有充分理由的情况下添加它们(非托管句柄等)


重新终结顺序:不,没有遵循特定的顺序,因为完整的循环是可能的,并且需要在某个地方任意中断。

如果没有任何类型的引用路径可以到达对象,则无法检查该对象所占用的内存空间,除非或直到GC已经运行并使该内存空间可重用,并且创建了使用它的新对象;到那时,旧对象将不再存在。无论GC何时运行,对象在最后一次引用被破坏或变得不可访问时都会立即变得不可访问

具有活动终结器的对象始终具有引用路径,因为称为“终结队列”的数据结构包含对每个此类对象的引用。此队列中的对象是GC最后处理的对象;如果发现队列引用了某个对象,但没有其他引用,则引用将存储在名为“freachable”队列的结构中,该队列列出了其
Finalize
方法应在第一时间运行的对象。一旦GC循环完成,此列表将被视为强根引用,但终结器线程将开始从中提取内容。通常,一旦项目从列表中取出,在任何地方都不会有对它们的任何引用,它们将消失

弱引用增加了另一个折痕,因为即使存在对对象的弱引用,也会认为对象符合收集条件。将这些视为有效的最简单方法是,一旦确定了每个需要保留的对象,系统将检查并使每个目标不需要保留的
WeakReference
无效。一旦每个这样的
WeakReference
实例失效,对象将无法访问

原子语义可能起作用的唯一情况是当两个或多个
WeakReference
实例以同一对象为目标时