我在运行SQL脚本并将结果转储到文件的服务程序中遇到内存泄漏问题。运行产生许多结果行的查询后,进程的内存使用量每次增加50 MB以上,并且不会降低。
以下是打开连接并检索结果的代码:
using (var conn = new SqlConnection(DataSourceInfo.ConnectionString))
{
conn.Open();
var scmd = new SqlCommand(query_string, conn);
scmd.CommandTimeout = 86400;
var writer = dest.GetStream(); //the writer is disposed of elsewhere
using (var da = new SqlDataAdapter(scmd))
using (var ds = new DataSet())
{
da.Fill(ds);
var table = ds.Tables[0];
var rows = table.Rows;
if (TaskInfo.IncludeColNames.Value)
{
object[] cols = new object[table.Columns.Count];
for(int i = 0; i < table.Columns.Count; i++)
cols[i] = table.Columns[i];
LineFormatter(writer, TaskInfo.FieldDelimiter, null, false, cols);
writer.WriteLine();
}
foreach(System.Data.DataRow r in rows)
{
var fields = r.ItemArray;
LineFormatter(writer, TaskInfo.FieldDelimiter, TaskInfo.TextQualifier, TaskInfo.TrimFields.Value, fields);
writer.WriteLine();
}
}
}
我使用带有sos.dll的WinDbg在执行完成后按类型列出顶级对象,并且该过程有足够的时间用于GC:
79333470 101 166476 System.Byte[]
65245dcc 177 3897420 System.Data.RBTree`1+Node[[System.Data.DataRow, System.Data]][]
0015e680 5560 3968936 Free
79332b9c 342 3997304 System.Int32[]
6524508c 120349 7702336 System.Data.DataRow
793041d0 984 22171736 System.Object[]
7993bec4 70 63341660 System.Decimal[]
79330a00 2203630 74522604 System.String
第二列是对象数量,第三列是总大小。
不应该有任何未完成的System.Data.DataRow对象。它看起来好像在某种程度上被泄露,但我不确定如何。
我做错了什么?
注意:以前的版本使用SqlDataReader来检索行数据,但是这种方法缺少获取列标题(我知道)的方法,并且在DataSet和SqlDatReader之间共享数据集会在某些查询中无声地失败。我不记得那个版本有内存泄漏问题。
答案 0 :(得分:2)
选择一个DataRow并使用!gcroot
查看谁在行中保留引用。请参阅Tracking down managed memory leaks (how to find a GC leak)。
答案 1 :(得分:2)
除非LineFormatter正在做一些事情来保留程序生命周期的引用,否则我认为这里没有问题。
您正在对垃圾收集器的工作方式做出一些重大假设。 AFAIK,它的工作基于记忆压力,而不是时间。如果您感觉非常偏执,可以在代码中运行GC.Collect()并查看是否会降低内存使用率,但我绝不会在生产代码中调用GC.Collect() - 只需将其作为测试
还要确保您不依赖任务管理器来告诉您.NET堆中保留了多少内存。相反,您应该查看performance counters in PerfMon来检查托管世界中发生的情况。
答案 2 :(得分:0)
追踪内存泄漏的最佳方法是使用分析器,例如Nant或.Net Memory Profiler。我认为两者都至少有15天的试用期,这足以了解您的需求并诊断内存泄漏。
我使用过.Net Memory Profiler。它非常适合准确地跟踪所持有的内容,以及从AppDomain或任何静态对象到达泄漏内存的路径。它的工作原理是运行您的应用并抓取元数据;你拍摄快照(使用探查器),运行一个泄漏emory的操作,然后进行第二个snapshop并进行比较。您可以隔离两个快照之间的不同之处,并按大小排序,以便快速关闭问题。非常好的工具!
答案 3 :(得分:0)
您可能需要将SqlCommand放入使用块中,或手动处理它。