Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Garbage Collection and Using - Why is memory not released after `using{}` block?

I've recently been some refactoring some old database access code. I have a library of hundreds of methods which look something like this

public int getFoo(int id)
{
    using(SqlConnection connection = ConnectionManager.GetConnection())
    {
        string sql = "SELECT TOP(1) foo FROM bar WHERE id=@id";

        SqlCommand command = new SqlCommand(sql, connection);
        command.Parameters.AddWithValue("@id", id);
        return (int)command.ExecuteScalar();
    }
}

I figured it would be a sensible thing to wrap the SqlCommands into a using{} block (like the SqlConnection already is) so that resources get disposed of as soon as possible. Out of intellectual curiosity I decided to make the following little console application to see how much memory would be freed up:

using (SqlConnection conn = ConnectionManager.GetConnection())
{
    WeakReference reference;
    string sql = "SELECT COUNT(foo) FROM bar";
    Console.WriteLine("Memory Allocated before SqlCommand: " + GC.GetTotalMemory(true));
    using (SqlCommand comm = new SqlCommand(sql,conn))
    {
        Console.WriteLine("Memory Allocated after SqlCommand: " + GC.GetTotalMemory(true));
        reference = new WeakReference(comm);

        Console.WriteLine("SQL output: " + comm.ExecuteScalar());

        Console.WriteLine(
            "Memory Allocated before dispose SqlCommand: " + GC.GetTotalMemory(true));
    }
    GC.Collect();
    Console.WriteLine("Memory Allocated after SqlCommand: " + GC.GetTotalMemory(true));
    Console.WriteLine("Reference is alive: " + reference.IsAlive);

    Console.ReadLine();
}

To my surprise this is the output I got:

Memory Allocated before SqlCommand: 236384

Memory Allocated after SqlCommand: 239160

SQL output: (whatever)

Memory Allocated before dispose SqlCommand: 246416

Memory Allocated after dispose SqlCommand: 246548 <-- It's gone up!?

Reference is alive: True <-- Why is reference still alive?

At first I though maybe my WeakReference might be somehow keeping command alive, but I commented that code out and still I got a similar result.

Why is command not being garbage collected here, even when GC.Collect() has been explicitly called? If a variable is introduced in a using block when can we expect that variable to be eligible for garbage collection?

like image 656
Slappywag Avatar asked Dec 19 '22 04:12

Slappywag


1 Answers

Disposing of objects has nothing to do with garbage collection. Garbage collection has to do with cleaning up of managed resources. Disposal is there to clean up unmanaged resources that the GC isn't tracking.

Sometimes those unmanaged resources are memory allocated explicitly without going through the GC, sometimes they're locks on a file handle, a database connection, a network socket, or any number of other possibilities. But whatever they are, they very explicitly won't be the memory that the GC is tracking, which is what you're measuring.

As far as accounting for the differences, your differences are simply within the noise level of the program. Your changes aren't meaningfully affecting how much managed memory is used, and the differences you're seeing are consistent with the normal fluctuation of memory in a program that uses garbage collection.

like image 124
Servy Avatar answered Jan 11 '23 23:01

Servy