我最近一直在重构一些旧的数据库访问代码。我有一个包含数百种方法的库,看起来像这样
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块中引入了变量,我们可以期望该变量有资格进行垃圾收集吗?
答案 0 :(得分:9)
处理对象时 nothing 与垃圾收集有关。垃圾收集与清理托管资源有关。处理是为了清理GC 未跟踪的非托管资源。
有时,这些非托管资源是显式分配的内存,无需通过GC,有时它们会锁定文件句柄,数据库连接,网络套接字或任何其他可能性。但无论它们是什么,它们都非常明确地不会成为GC正在跟踪的内存,这就是您正在测量的内容。
就差异而言,您的差异只是在程序的噪音水平范围内。您的更改不会影响使用的托管内存量,并且您看到的差异与使用垃圾回收的程序中内存的正常波动一致。
答案 1 :(得分:1)
在处理SqlConnection
后,如果它是该程序中使用的第一个连接,我会期望内存使用略有增加。
SqlConnection.Dispose()
和SqlConnection.Close()
将存储用于管理静态线程安全集合中的连接的内部类,以便下一个SqlConnection.Open()
可以使用该对象而不是去通过建立与数据库的另一个连接的过程。
线程安全集合将占用一些自己的内存,即使它已经存在,处理对连接的引用的内部对象也会导致内部调整大小。
因此我希望这里的尺寸略有增加。
如果在使用块中引入了变量,我们可以期望该变量有资格进行垃圾收集吗?
我们当然不会!
想想GC集合的作用。
它首先要做的是标记它不收集的所有对象。
如果某个对象由static
引用持有,则无法收集该对象。
如果某个对象被其中一个活动线程堆栈上的变量引用,则无法收集该对象。
如果某个对象被无法收集的对象中的字段引用,则无法收集该对象。
现在,当你在那时做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为我们处理了这个问题。
当对象被显示为可收集时,它们被收集;这个可以在最后一次使用它们之后的任何时间发生,但可能直到最后一次使用它们的方法返回之后才会发生,直到那时在堆栈上引用它们的内存片段可能仍然指向它们的位置。