Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GC Behavior Inconsistent Between 32-bit and 64-bit Applications

I have noticed inconsistent behavior from the GC when compiling console applications under both 32-bit and 64-bit in .Net 4.0 using VS 2013.

Consider the following code:

class Test
{
    public static bool finalized = false;
    ~Test()
    {
        finalized = true;
    }
}

and in Main() ...

var t = new Test();
t = null;
GC.Collect();
GC.WaitForPendingFinalizers();
if (!Test.finalized)
    throw new Exception("oops!");

When running in 64-bit (debug) mode this works every time without fail; however, running in 32-bit mode I cannot force this object to get collected (even if I create more objects and wait a period of time, which I have tried).

Does anyone have any ideas as to why this is? It's causing me trouble when trying to debug some code that must deal with releasing unmanaged proxy data for the 32-bit version of the assemblies. There's a lot of objects in 32-bit mode that just sit there until a long time later (no so in 64-bit).

I'm trying to debug something in 32-bit mode, but the finalizers are not getting called (at least, not by force). The objects just sit there and never get collected (I can see all the weak references still having a value). In 64-bit mode, all the weak references are cleared as expected, and all finalizers get called.

Note: Though the code above is on a very small scale, I have noticed in 32-bit mode many more objects stuck in the GC until more objects get created later (even when "Collect" and "WaitForPendingFinalizers" is called). This is never the case in 64-bit mode. I have one user wondering why so many objects where not getting collected, which caused me to investigate, to which I found out that everything seems to work better in 64-bit mode than 32. Just trying to understand why.

Edit: Here is better code to show the differences:

class Program
{
    class Test
    {
        public static bool Finalized = false;
        public int ID;
        public Test(int id)
        {
            ID = id;
        }
        ~Test()
        { // <= Put breakpoint here
            Finalized = true;
            Console.WriteLine("Test " + ID + " finalized.");
        }
    }

    static List<WeakReference> WeakReferences = new List<WeakReference>();

    public static bool IsNet45OrNewer()
    {
        // Class "ReflectionContext" exists from .NET 4.5 onwards.
        return Type.GetType("System.Reflection.ReflectionContext", false) != null;
    }

    static void Main(string[] args)
    {
        Console.WriteLine("Is 4.5 or newer: " + IsNet45OrNewer());
        Console.WriteLine("IntPtr: " + IntPtr.Size + Environment.NewLine);

        Console.WriteLine("Creating the objects ...");

        for (var i = 0; i < 10; ++i)
            WeakReferences.Add(new WeakReference(new Test(i)));

        Console.WriteLine("Triggering collect ...");
        GC.Collect();

        Console.WriteLine("Triggering finalizers ..." + Environment.NewLine);
        GC.WaitForPendingFinalizers();

        Console.WriteLine(Environment.NewLine + "Checking for objects still not finalized ...");

        bool ok = true;

        for (var i = 0; i < 10; ++i)
            if (WeakReferences[i].IsAlive)
            {
                var test = (Test)WeakReferences[i].Target;
                if (test != null)
                    Console.WriteLine("Weak references still exist for Test " + test.ID + ".");
                ok = false;
            }

        if (ok)
            Console.WriteLine("All Test objects successfully collected and finalized.");

        Console.WriteLine(Environment.NewLine + "Creating more objects ...");

        for (var i = 0; i < 10; ++i)
            WeakReferences.Add(new WeakReference(new Test(i)));

        Console.WriteLine("Triggering collect ...");
        GC.Collect();

        Console.WriteLine("Triggering finalizers ..." + Environment.NewLine);
        GC.WaitForPendingFinalizers();

        Console.WriteLine(Environment.NewLine + "Checking for objects still not finalized ...");

        ok = true;

        for (var i = 0; i < 10; ++i)
            if (WeakReferences[i].IsAlive)
            {
                var test = (Test)WeakReferences[i].Target;
                if (test != null)
                    Console.WriteLine("Weak references still exist for Test " + test.ID + ".");
                ok = false;
            }

        if (ok)
            Console.WriteLine("All Test objects successfully collected and finalized.");

        Console.WriteLine(Environment.NewLine + "Done.");

        Console.ReadKey();
    }
}

It works in 64-bit, but not in 32-bit. On my system, "Test #9" never gets collected (a weak reference remains), even after creating more objects, and trying a second time.

FYI: The main reason for asking the question is because I have a \gctest option in my console to test garbage collection between V8.Net and the underlying V8 engine on the native side. It works in 64-bit, but not 32.

like image 931
James Wilkins Avatar asked May 15 '15 08:05

James Wilkins


People also ask

What happens when you run 32 bit on 64 bit?

To put it in simple words, if you run a 32-bit program on a 64-bit machine, it will work fine, and you won't encounter any problems. Backward compatibility is an important part when it comes to computer technology. Therefore, 64 bit systems can support and run 32-bit applications.

What is Gc collect in C#?

It performs a blocking garbage collection of all generations. All objects, regardless of how long they have been in memory, are considered for collection; however, objects that are referenced in managed code are not collected. Use this method to force the system to try to reclaim the maximum amount of available memory.


1 Answers

No repro on VS2013 + .NET 4.5 on x86 and x64:

class Program
{
    class Test
    {
        public readonly int I;

        public Test(int i)
        {
            I = i;
        }

        ~Test()
        {
            Console.WriteLine("Finalizer for " + I);
        }
    }

    static void Tester()
    {
        var t = new Test(1);
    }

    public static bool IsNet45OrNewer()
    {
        // Class "ReflectionContext" exists from .NET 4.5 onwards.
        return Type.GetType("System.Reflection.ReflectionContext", false) != null;
    }

    static void Main(string[] args)
    {
        Console.WriteLine("Is 4.5 or newer: " + IsNet45OrNewer());
        Console.WriteLine("IntPtr: " + IntPtr.Size);

        var t = new Test(2);
        t = null;

        new Test(3);

        Tester();

        Console.WriteLine("Pre GC");
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Post GC");

        Console.ReadKey();
    }
}

Note that (as noticed by @Sriram Sakthivel), there could be "ghost" copies of your variable around, that have their lifespan extended until the end of the method (when you compile the code in Debug mode or you have a debugger attached (F5 instead of Ctrl+F5), the lifespan of your variables is extended until the end of the method to ease debugging)

For example object initialization (when you do something like new Foo { I = 5 }) creates an hidden local variable.

like image 51
xanatos Avatar answered Nov 09 '22 02:11

xanatos