C# 垃圾收集和使用-为什么在`使用{}`块之后不释放内存?

C# 垃圾收集和使用-为什么在`使用{}`块之后不释放内存?,c#,.net,garbage-collection,using,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 =

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

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
s包装成
(就像
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输出:(任意)

dispose SqlCommand之前分配的内存:246416

dispose SqlCommand:246548处理对象后分配的内存与垃圾收集无关。垃圾收集与清理托管资源有关。Disposition用于清理GC未跟踪的非托管资源

有时,这些非托管资源是在不经过GC的情况下显式分配的内存,有时是文件句柄上的锁、数据库连接、网络套接字或任何其他可能性。但不管它们是什么,它们都不会是GC正在跟踪的内存,这是您正在测量的


就差异的原因而言,您的差异只是在程序的噪音级别内。您所做的更改并没有对托管内存的使用量产生任何影响,您所看到的差异与使用垃圾收集的程序中内存的正常波动是一致的。

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

SqlConnection.Dispose()
SqlConnection.Close()
默认情况下将用于管理静态线程安全集合中的连接的内部类存储在一个静态线程安全集合中,以便下一个
SqlConnection.Open()
可以使用该对象,而不必执行建立到数据库的另一个连接的过程

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

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

如果在using块中引入变量,我们何时可以期望该变量符合垃圾收集的条件

我们当然不会

考虑GC集合的作用

它做的第一件事是标记所有它无法收集的对象

  • 如果对象由
    静态
    引用持有,则无法收集该对象

  • 如果某个对象被某个活动线程堆栈上的变量引用,则无法收集该对象

  • 如果对象中的字段引用了无法收集的对象,则无法收集该对象

  • 现在,当您执行
    sqlcomm=newsqlcommand(sql,conn)
    时,当前线程堆栈上的一个单词被设置为指向构造函数创建的对象

    然后,堆栈上的该位置由每次使用
    comm
    时抖动产生的机器代码使用

    作为实现问题,堆栈上可能有也可能没有该指针的其他副本

    在代码中最后一次使用对象(使用
    块的
    结尾导致的隐藏隐式
    comm.Dispose()
    )之后,堆栈中的这些字可以被重用

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

    如果方法的其余部分没有任何代码使用堆栈上的相同空间(您没有),那么它们就不太可能出现。例如,如果您使用
    结束之后和
    GC.Collect()之前添加了一个
    object which=new object()
    那么您很可能会发现
    引用。IsAlive
    是错误的,因为抖动可能会使用堆栈中新引用的那部分内存来引用
    whatever
    (特别是如果在
    GC.Collect()之后使用了
    whatever
    ,这样就不会完全优化)

    使用
    就是调用
    Dispose()
    ,而
    Dispose()
    的非常的要点是它与托管内存无关;如果有,那么我们就不需要它了,因为GC会为我们处理它


    当对象显示为可收集时,收集对象;该可以在最后一次使用引用后的任何时间发生,但可能在最后一次使用引用的方法返回后才会发生,就像在工件返回之前一样在堆栈上引用它们的内存数量可能仍然引用它们的位置。

    在我之后重复:
    使用
    与垃圾收集无关。其目的是释放非托管资源。因此,由
    comm
    持有的非托管资源可能已被释放,但
    comm
    本身是活动的,并且运行良好。内存实际释放的时间不确定。特别是当您在与您试图发布的内容相同的范围内调用GC.Collect()时。如果您确实想确定内存使用情况,则需要使用探查器。还有@FrédéricHamidi说的话using语句只需在块后的对象上调用“Dispose”ends@Slappywag不管你是否处理它,这都是真的。也许是真的。但是,所使用的内存的正常变化高于Sql中的少数内存资源