垃圾收集和使用 - 为什么在 `using{}` 块后没有释放内存?
Garbage Collection and Using - Why is memory not released after `using{}` block?
我最近一直在重构一些旧的数据库访问代码。我有一个包含数百种方法的库,看起来像这样
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();
}
令我惊讶的是,这是我得到的输出:
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?
起初我虽然我的 WeakReference
可能以某种方式让 command
存活,但我注释掉了该代码,但我仍然得到了类似的结果。
为什么 command
在这里没有被垃圾回收,即使 GC.Collect()
已被显式调用?如果在 using 块中引入了一个变量,我们什么时候可以期望该变量符合垃圾回收条件?
处理对象与垃圾收集没有任何关系。垃圾收集与清理 托管 资源有关。处置是为了清理 GC 未跟踪 的 非托管 资源。
有时这些非托管资源是在不通过 GC 的情况下显式分配的内存,有时它们锁定在文件句柄、数据库连接、网络套接字或任何数量的其他可能性上。但无论它们是什么,它们非常明确地 不会 是 GC 正在跟踪的内存,这就是你正在测量的内存。
就差异而言,您的差异只是在程序的噪音水平之内。您的更改不会对使用多少托管内存产生有意义的影响,您看到的差异与使用垃圾回收的程序中内存的正常波动一致。
在处理 SqlConnection
后,如果它是该程序中使用的第一个连接,我预计内存使用会略有增加。
SqlConnection.Dispose()
和SqlConnection.Close()
会默认将内部用来管理连接的class存储在一个静态线程安全的集合中,这样下一个SqlConnection.Open()
就可以使用该对象,而不是通过建立另一个数据库连接的过程。
线程安全集合将占用它自己的一些内存,即使它已经存在,处理对连接的引用的内部对象也会导致内部向上调整大小。
因此我希望这里的尺寸会略有增加。
If a variable is introduced in a using block when can we expect that variable to be eligible for garbage collection?
我们肯定不会!
想一想GC收集的作用。
它做的第一件事是标记它可以不能收集的所有对象。
如果对象由 static
引用持有,则无法收集。
如果某个对象被活动线程堆栈之一上的变量引用,则无法收集。
如果一个对象被对象中的某个字段引用到无法采集,那么就无法采集。
现在,当您执行 SqlCommand comm = new SqlCommand(sql,conn)
时,当前线程堆栈上的一个字被设置为指向构造函数创建的对象。
每次使用 comm
.
时,抖动产生的机器代码会使用堆栈上的那个位置。
根据实现情况,该指针的其他副本可能会也可能不会被放入堆栈。
在代码中最后一次使用该对象后(using
块末尾导致的隐藏隐式 comm.Dispose()
),那么堆栈中的那些词 可以 被重用。
他们可能不是。如果您在调试模式下编译,它们肯定不会,因为它会混淆调试,让变量在它们仍在范围内时突然消失。
如果方法的其余部分没有任何代码在堆栈上使用相同的 space,而您没有,则它们不太可能是。例如,如果您在 using
结束之后和 GC.Collect()
之前添加了一个 object whatever = new object()
,那么您更有可能发现 reference.IsAlive
是错误的,因为抖动 可能 使用对 whatever
的新引用的那部分堆栈内存(特别是如果在 GC.Collect()
之后使用了 whatever
以便它不只是完全优化掉了)。
using
是调用Dispose()
,而Dispose()
的非常点是与托管内存无关;如果是这样,那么我们就不需要它了,因为 GC 会为我们处理。
当对象可以显示为可收集时,它们就会被收集;这 可以 在最后一次使用它们的引用之后的任何时间发生,但是 可能 在最后一次使用它们的方法之后才会发生returns,直到那时在堆栈中引用它们的内存片段可能仍会引用它们的位置。
我最近一直在重构一些旧的数据库访问代码。我有一个包含数百种方法的库,看起来像这样
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();
}
令我惊讶的是,这是我得到的输出:
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?
起初我虽然我的 WeakReference
可能以某种方式让 command
存活,但我注释掉了该代码,但我仍然得到了类似的结果。
为什么 command
在这里没有被垃圾回收,即使 GC.Collect()
已被显式调用?如果在 using 块中引入了一个变量,我们什么时候可以期望该变量符合垃圾回收条件?
处理对象与垃圾收集没有任何关系。垃圾收集与清理 托管 资源有关。处置是为了清理 GC 未跟踪 的 非托管 资源。
有时这些非托管资源是在不通过 GC 的情况下显式分配的内存,有时它们锁定在文件句柄、数据库连接、网络套接字或任何数量的其他可能性上。但无论它们是什么,它们非常明确地 不会 是 GC 正在跟踪的内存,这就是你正在测量的内存。
就差异而言,您的差异只是在程序的噪音水平之内。您的更改不会对使用多少托管内存产生有意义的影响,您看到的差异与使用垃圾回收的程序中内存的正常波动一致。
在处理 SqlConnection
后,如果它是该程序中使用的第一个连接,我预计内存使用会略有增加。
SqlConnection.Dispose()
和SqlConnection.Close()
会默认将内部用来管理连接的class存储在一个静态线程安全的集合中,这样下一个SqlConnection.Open()
就可以使用该对象,而不是通过建立另一个数据库连接的过程。
线程安全集合将占用它自己的一些内存,即使它已经存在,处理对连接的引用的内部对象也会导致内部向上调整大小。
因此我希望这里的尺寸会略有增加。
If a variable is introduced in a using block when can we expect that variable to be eligible for garbage collection?
我们肯定不会!
想一想GC收集的作用。
它做的第一件事是标记它可以不能收集的所有对象。
如果对象由
static
引用持有,则无法收集。如果某个对象被活动线程堆栈之一上的变量引用,则无法收集。
如果一个对象被对象中的某个字段引用到无法采集,那么就无法采集。
现在,当您执行 SqlCommand comm = new SqlCommand(sql,conn)
时,当前线程堆栈上的一个字被设置为指向构造函数创建的对象。
每次使用 comm
.
根据实现情况,该指针的其他副本可能会也可能不会被放入堆栈。
在代码中最后一次使用该对象后(using
块末尾导致的隐藏隐式 comm.Dispose()
),那么堆栈中的那些词 可以 被重用。
他们可能不是。如果您在调试模式下编译,它们肯定不会,因为它会混淆调试,让变量在它们仍在范围内时突然消失。
如果方法的其余部分没有任何代码在堆栈上使用相同的 space,而您没有,则它们不太可能是。例如,如果您在 using
结束之后和 GC.Collect()
之前添加了一个 object whatever = new object()
,那么您更有可能发现 reference.IsAlive
是错误的,因为抖动 可能 使用对 whatever
的新引用的那部分堆栈内存(特别是如果在 GC.Collect()
之后使用了 whatever
以便它不只是完全优化掉了)。
using
是调用Dispose()
,而Dispose()
的非常点是与托管内存无关;如果是这样,那么我们就不需要它了,因为 GC 会为我们处理。
当对象可以显示为可收集时,它们就会被收集;这 可以 在最后一次使用它们的引用之后的任何时间发生,但是 可能 在最后一次使用它们的方法之后才会发生returns,直到那时在堆栈中引用它们的内存片段可能仍会引用它们的位置。