knick
knick

Reputation: 971

Garbage Collection - Is it always safe to access objects via strong references?

My understanding is that when the GC finds a sub-graph of objects that are no longer accessible (via strong references) from the main graph, it will collect them up and reclaim the memory. My question is concerning the order in which inaccessible objects are deleted. Does this occur as an atomic operation? Are all of the inaccessible objects finalized at once, or does the GC finalize each object one-by-one while the application is still executing? If the objects are finalized one-by-one, is there a particular order that is followed?

If I have an object A that holds a weak reference to object B, then it is clear that A must check if B is still alive before invoking any of B’s instance methods. Now suppose B holds a strong reference to another object C. If B is still alive, am I always guaranteed that C will also still be alive? Is there any possibility that the GC might have marked both B & C for collection, but C is finalized before B?

My guess is that it will always be safe to access C from B (since this is a strong reference). But I would like to confirm this assumption, because if I am wrong I could introduce a very intermittent hard-to-track-down 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.
    }
}

Upvotes: 3

Views: 321

Answers (4)

supercat
supercat

Reputation: 81179

If there exists no reference path of any sort which can reach an object, there will be no means to examine the memory space that object used to occupy, unless or until such time as the GC has run and made that memory space usable for reuse and some new object is created that uses it; by the time that occurs, the old object won't exist anymore. Regardless of when the GC runs, an object becomes instantly inaccessible the moment the last reference to it is destroyed or becomes inaccessible.

Objects with active finalizers always have a reference path, since a data structure called the "finalization queue" holds a reference to every such object. Objects in this queue are the last things processed by the GC; if an object is found to be referenced by the queue but by nothing else, a reference will be stored in a structure called the "freachable" queue, which lists objects whose Finalize method should be run at first opportunity. Once a GC cycle completes, this list will be considered a strong rooted reference, but the finalizer thread will start pulling things out of it. Typically, once items get pulled from the list there won't be any reference to them anywhere and they'll disappear.

Weak references add another wrinkle, since objects are considered eligible for collection even if weak references to them exist. The simplest way to regard those as working is to figure that once every object requiring retention has been identified, the system will go through and invalidate every WeakReference whose targets do not require retention. Once every such WeakReference instances has been invalidated, the object will be inaccessible.

The only situation where atomic semantics might matter would be when two or more WeakReference instances target the same object. In that scenario, it might theoretically be possible for one thread to access the Target property of a WeakReference at the exact moment that the GC was invalidating another WeakReference which had the same target. I don't think this situation can actually arise; it could be prevented by having multiple WeakReference instances that share the same target also share their GCHandle. In that case, either the access to the target would happen soon enough to keep the object alive, or else the handle would be invalidated, effectively invalidating all WeakReference instances that hold a reference to it.

Upvotes: 0

dthorpe
dthorpe

Reputation: 36082

In normal managed methods, you can rely on everything that B refers to with strong references to be alive as long as B is alive. The reference to C from B will be good for the lifetime of B.

The exception is in finalization code. If you implement a finalizer (which should be considered an unusual case, not the norm), then all bets are off within the call chain of the finalizer. Finalizers may not execute for a long time after the GC notices that the objects are no longer referenced by live objects - objects with finalizers tend to hang around a lot longer than "normal" objects, which is another reason not to use finalizers. A finalizer may not execute at all, ever, in extreme panic shutdown corner cases. You can't assume anything about what thread the finalizer will execute on, or what order finalizers will execute in. And you should avoid referring to any reference variables in your object because you don't know if they have already been finalized.

This is all academic though. Your code example doesn't implement a finalizer, so you don't need to worry about the bizzarro finalizer world.

Upvotes: 2

Marc Gravell
Marc Gravell

Reputation: 1062895

Yes, if your ClassB has a reference to a ClassC instance via objCStrongRef, then if the ClassB is still alive you don't need to worry about the ClassC evaporating at random. The exception to this is if you write a finalizer, i.e.

~ClassB() { ...}

In there, you should not attempt to talk to objCStrongRef at all; because you can have no clue which object gets finalized first. If ClassC needs something at finalization, it should have a separate ~ClassC() - although in reality finalizer methods are really rare and you shouldn't add them without good reason (unmanaged handles, etc)

Re finalization order: no, there is no particular order followed, because full loops are possible - and it needs to be broken somewhere arbitrarily.

Upvotes: 4

Dennis
Dennis

Reputation: 37770

From the "CLR via C#" by Jeffrey Richter:

Furthermore, be aware of the fact that you have no control over when the Finalize method will execute. Finalize methods run when a garbage collection occurs, which may happen when your application requests more memory. Also, the CLR doesn’t make any guarantees as to the order in which Finalize methods are called, so you should avoid writing a Finalize method that accesses other objects whose type defines a Finalize method; those other objects could have been finalized already. However, it is perfectly OK to access value type instances or reference type objects that do not define a Finalize method. You also need to be careful when calling static methods because these methods can internally access objects that have been finalized, causing the behavior of the static method to become unpredictable.

Upvotes: 0

Related Questions