GC.Collect() not collecting immediately?
Asked Answered
L

4

5

In the course of a discussion in chat, I wrote this console application.

Code:

using System;

class Program
{
    static void Main(string[] args)
    {
        CreateClass();
        Console.Write("Collecting... ");
        GC.Collect();
        Console.WriteLine("Done");
    }

    static void CreateClass()
    {
        SomeClass c = new SomeClass();
    }
}

class SomeClass
{
    ~SomeClass()
    {
        throw new Exception();
    }
}

Result:

Collecting... Done

Unhandled Exception: System.Exception: Exception of type 'System.Exception' was
thrown.
   at SomeClass.Finalize()

I would have expected the app to crash before Done was printed.

I don't care much about how to make it. My question is, why doesn't it?

Lilongwe answered 5/7, 2012 at 23:49 Comment(4)
Possible duplicate of: #3500074Wilds
Below link explains the behavior of System.GC(), and the options with which it can be called: https://mcmap.net/q/1866466/-c-destructor-not-working-as-expectedWilds
This is what GC.WaitForPendingFinalizers is for: msdn.microsoft.com/en-us/library/…Shillyshally
Throw in a WaitForPendingFinalizers as you are testing when finalizer are called not when collection happens.Sukkah
V
12

Objects with finalizers cannot be collected within a single garbage collection procedure. Such objects are moved to f-reachable queue, and remain there until finalizers are called. Only after that they can be garbage-collected.

Following code is better, but you should not rely on it anyway:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Also, throwing exceptions in finalizer seems too brutal for me, even for testing purposes.

Also, interesting side-effect of finalizers: an object with finalizer can still 'resurrect' itself (effectively prevent garbage collection of itself), if stores this reference in finalizer (assigns it to some static variable).

Vasili answered 5/7, 2012 at 23:55 Comment(2)
I find if I sleep for about 100ms then the finalizer runs before I reach the WriteLine. The finalizer gets called quite quickly (but not quickly enough). Do you have any comments on that behaviour?Lilongwe
Yes, that is because finalizers are called on a separate thread.Vasili
D
6

Did you read the documentation?

Use this method to try to reclaim all memory that is inaccessible.

It is not a command, it is a request, which may or may not work out as you would like. This is not often a good idea anyway (sometimes many, many small, short-lived objects are created as a result of some process, in which case it may be beneficial to call GC.Collect, but this is rare).

Since it doesn't seem like you are trying to solve a real problem and are instead toying around with the GC this is the best advice I have to offer.

Disrelish answered 5/7, 2012 at 23:53 Comment(0)
S
3

In the most common garbage-collector implementations, no managed user code can be run during a garbage-collection cycle. Finalize methods count as user code. Although it would be theoretically possible for the system to freeze all other user code while Finalize methods execute, this behavior would increase the apparent cost of garbage collection on multi-core systems and also increase the likelihood of deadlock. To avoid these issues, the system does not run Finalize methods as part of garbage collection, but instead builds a list of objects that need to have their Finalize methods run (the list is called the "freachable queue"). The list itself is considered a rooted reference, so any object which is referred to by an object in the freachable queue will be considered to be strongly rooted at least until such time as the system retrieves the freachable object from the queue, runs its Finalize method, and discards the reference.

Microsoft's early documentation regarding finalization is very confusing, since it suggests that objects to which finalizable objects hold references might not exist when those methods run. In fact, all such objects are guaranteed to exist; what is uncertain is whether they will have already had their Finalize methods run.

Scowl answered 6/7, 2012 at 15:11 Comment(0)
W
0
  1. GC.Collect() put those objects which are NOT referenced and has finalizer method to finalizer queue.
  2. it will clear out finalizer queue with calling to finalize methods.

If the above two points are clear, then your code holds a reference to SomeClass in a static method. That means it is still alive until the Program's main method is executing.

If you want your app to crash before printing 'done', then first nullify your SomeClass object and then call GC.Collect. It will put your object in finalizer queue but again its GC's wish when to clear out that queue. If you want GC to clear out that queue and call finalizer, then call GC.WaitForPendingFinalizers(). Your thread will wait until your finalizer is called and it will then proceed. I modified your code for desired output. Instead of throwing exception I printed a statement in finalizer.

class Program
    {
        static void Main(string[] args)
        {
            CreateClass();
            Console.Write("Collecting... ");
            GC.Collect();
            GC.WaitForPendingFinalizers();
            Console.WriteLine("Done");

            Console.ReadLine();
        }

        static void CreateClass()
        {
            SomeClass c = new SomeClass();
            c = null;
        }
    }

    class SomeClass
    {
        ~SomeClass()
        {
            Console.WriteLine("Finalized...");
        }
    }
Wootten answered 9/10, 2013 at 11:43 Comment(1)
About nullifying the object, isn't it in another stack frame? Being it in another method the stack frame should be freed automatically without the need of nulling variables for them to be garbage collected.Detonation

© 2022 - 2024 — McMap. All rights reserved.