垃圾收集和使用 - 为什么在使用{}`块后没有释放内存?

时间:2015-05-20 15:16:39

标签: c# .net garbage-collection using

我最近一直在重构一些旧的数据库访问代码。我有一个包含数百种方法的库,看起来像这样

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();
    }
}

我认为将SqlCommand封装到using{}块(就像SqlConnection已经存在)是一个明智的做法,这样资源就会尽快被处理掉。出于好奇心,我决定制作以下小型控制台应用程序,以查看将释放多少内存:

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();
}

令我惊讶的是这是我得到的输出:

  

在SqlCommand之前分配的内存:236384

     

在SqlCommand之后分配的内存:239160

     

SQL输出:(无论如何)

     

在配置SqlCommand之前分配的内存:246416

     

处理完SqlCommand后分配的内存:246548< - 它已经上升了!?

     

引用是活的:真的< - 为什么引用还活着?

起初我可能我的WeakReference可能会以某种方式保持command活着,但我评论了代码,但我仍然得到了类似的结果。

为什么command在这里不被垃圾收集,即使明确调用了GC.Collect()?如果在using块中引入了变量,我们可以期望该变量有资格进行垃圾收集吗?

2 个答案:

答案 0 :(得分:9)

处理对象时 nothing 与垃圾收集有关。垃圾收集与清理托管资源有关。处理是为了清理GC 未跟踪的非托管资源

有时,这些非托管资源是显式分配的内存,无需通过GC,有时它们会锁定文件句柄,数据库连接,网络套接字或任何其他可能性。但无论它们是什么,它们都非常明确地不会成为GC正在跟踪的内存,这就是您正在测量的内容。

就差异而言,您的差异只是在程序的噪音水平范围内。您的更改不会影响使用的托管内存量,并且您看到的差异与使用垃圾回收的程序中内存的正常波动一致。

答案 1 :(得分:1)

在处理SqlConnection后,如果它是该程序中使用的第一个连接,我会期望内存使用略有增加。

默认情况下,

SqlConnection.Dispose()SqlConnection.Close()将存储用于管理静态线程安全集合中的连接的内部类,以便下一个SqlConnection.Open()可以使用该对象而不是去通过建立与数据库的另一个连接的过程。

线程安全集合将占用一些自己的内存,即使它已经存在,处理对连接的引用的内部对象也会导致内部调整大小。

因此我希望这里的尺寸略有增加。

  

如果在使用块中引入了变量,我们可以期望该变量有资格进行垃圾收集吗?

我们当然不会!

想想GC集合的作用。

它首先要做的是标记它收集的所有对象。

  1. 如果某个对象由static引用持有,则无法收集该对象。

  2. 如果某个对象被其中一个活动线程堆栈上的变量引用,则无法收集该对象。

  3. 如果某个对象被无法收集的对象中的字段引用,则无法收集该对象。

  4. 现在,当你在那时做SqlCommand comm = new SqlCommand(sql,conn)时,当前线程堆栈上的一个单词被设置为指向构造函数创建的对象。

    然后,每次使用comm时,抖动产生的机器代码会使用堆栈上的那个位置。

    当实现很重要时,可能会有或没有该指针的其他副本放在堆栈上。

    在代码中最后一次使用对象(comm.Dispose()块的结尾导致隐藏的隐式using)之后,堆栈中的那些单词可以成为重复使用。

    他们可能不是。如果你在调试模式下编译它们肯定不会,因为它混淆了调试以使变量在仍在范围内时突然消失。

    如果方法的其余部分没有任何使用堆栈上相同空间的代码,则不太可能。例如,如果您在object whatever = new object()结束后和using之后添加了GC.Collect(),则您更有可能发现reference.IsAlive为假,因为抖动< strong>可能使用对whatever的新引用的堆栈内存的部分内容(特别是如果在whatever之后有一些GC.Collect()的使用,那么它就不是using只是完全优化了。)

    Dispose()是致电Dispose(),{{1}}的是与托管内存无关;如果确实如此,那么我们就不需要它,因为GC为我们处理了这个问题。

    当对象被显示为可收集时,它们被收集;这个可以在最后一次使用它们之后的任何时间发生,但可能直到最后一次使用它们的方法返回之后才会发生,直到那时在堆栈上引用它们的内存片段可能仍然指向它们的位置。