2017-05-30 48 views
1

我正在使用数据表在FIFO方法中保存运行的最后1000条日志消息。我将项目添加到数据表中并在大小增加到1000个项目后首先删除。但是,尽管数据表不超过1000个项目,但随着时间的推移内存会下降。DataTable.Row.Delete内存泄漏

样品:

DataTable dtLog = new DataTable(); 
for (int nLoop = 0; nLoop < 10000; nLoop++) 
{ 
    oLog LogType = new LogType(); 
    oLog.Name = "Message number " + nLoop; 

    dtLog.Rows.Add(oLog); 
    if (dtLog.Rows.Count > 1000) 
     dtLog.Rows.RemoveAt(0); 
}  

所以消息从数据表中删除,但记忆似乎并没有得到释放。我会期待内存被释放......?

或者有更好的方法来使用除了数据表之外的东西来执行运行日志?

+0

在垃圾收集器不能与您的代码同步的托管环境中,测量已用内存非常棘手。 – Steve

+2

听起来你想要一个队列? https://msdn.microsoft.com/en-us/library/7977ey2c(v=vs.110).aspx – AndyJ

+0

你需要一个反向计数器来正确处理这个问题..看看这个简单的例子在这里https:/ /stackoverflow.com/questions/5648339/deleting-specific-rows-from-datatable – MethodMan

回答

0

我不能说你的问题的内存泄漏部分,因为内存管理和垃圾收集.net使这是一个很难调查。

但是,我所能做的是建议除非必须,否则绝对不应该在.Net中使用DataTable。

现在,“从不”是一个非常强大的说法!这种事情需要有充分的理由进行备份。

所以,这些原因是什么? ... 内存使用情况。

我建立这个.NET小提琴:https://dotnetfiddle.net/wOtjw1

using System; 
using System.Collections.Generic; 
using System.Xml; 
using System.Data; 

public class DataObject 
{ 
    public string Name { get; set; } 
} 

public class Program 
{ 
    public static void Main() 
    { 
     Queue(); 
    } 

    public static void DataTable() 
    { 
     var dataTable = new DataTable(); 
     dataTable.Columns.Add("Name", typeof(string)); 

     for (int nLoop = 0; nLoop < 10000; nLoop++) 
     { 
      var dataObject = new DataObject(); 
      dataObject.Name = "Message number " + nLoop; 

      dataTable.Rows.Add(dataObject); 

      if (dataTable.Rows.Count > 1000) 
       dataTable.Rows.RemoveAt(0); 
     } 
    } 

    public static void Queue() 
    { 
     var queue = new Queue<DataObject>(); 

     for (int nLoop = 0; nLoop < 10000; nLoop++) 
     { 
      var dataObject = new DataObject(); 
      dataObject.Name = "Message number " + nLoop; 

      queue.Enqueue(dataObject); 

      if (queue.Count > 1000) 
       queue.Dequeue(); 
     } 
    } 
} 

运行两次,一次用数据表的方法,一旦与队列方法。每次

看看内存使用.NET小提琴报道:

DataTable的内存:2.74MB

队列存储器:1.46MB

这几乎一半的内存使用情况!我们所做的就是停止使用DataTable。

.Net DataTables是臭名昭着的内存饥饿。他们有相当好的理由,他们可以存储大量复杂的模式信息,并可以跟踪更改等。

这很好,但是......你需要这些功能吗?

不是?转储DT,使用System.Collections(.Generic)下的东西。

+0

我查看了Queue对象,但是我很难将其绑定到datagridview ...这是查看正在运行的后台服务的活动。这就是为什么我没有使用它的原因。如果我可以将它绑定到视图,我愿意使用队列。 – DaBlue

+0

这听起来像你应该发布一个新的问题。 – AndyJ

-1

无论何时修改/从DataTable删除行的旧/删除的数据仍然是由DataTable保持,直到调用DataTable.AcceptChanges

当的AcceptChanges被调用时,任何DataRow对象仍然在编辑模式下成功结束其编辑。 DataRowState也会更改:所有已添加和已修改的行都将保持不变,并删除已删除的行。

没有内存泄漏,因为这是设计。

作为一种替代方案,您可以使用circular buffer,它比排队更合适。

+0

谢谢,我将深入研究循环缓冲区! – DaBlue

+0

循环缓冲区通知是好的,但DataTable行在移除时不再有根。请参阅下面的答案,了解如何衡量实际的内存占用情况。删除DataTable后,DataRow对象不再有根。 –

-1

你的记忆被释放,但它并不那么容易看到。缺少一些工具(除了带有SOS的Windbg)以显示当前分配的内存减去死对象。 Windbg为此提供了!DumpHeap -live选项,仅显示活动对象。

我试图从AndyJ https://dotnetfiddle.net/wOtjw1

首先,我需要创建具有DataTable的内存转储有一个稳定的基线小提琴。 MemAnalyzer https://github.com/Alois-xx/MemAnalyzer是正确的工具。

MemAnalyzer.exe -procdump -ma DataTableMemoryLeak.exe DataTable.dmp 

这需要SysInternals的procdump在您的路径中。

现在你可以运行队列执行方案和托管堆上比较分配指标:

C>MemAnalyzer.exe -f DataTable.dmp -pid2 20792 -dtn 3 
     Delta(Bytes) Delta(Instances)  Instances  Instances2  Allocated(Bytes)  Allocated2(Bytes)  AvgSize(Bytes) AvgSize2(Bytes) Type 
     -176,624  -10,008     10,014   6    194,232     17,608     19    2934   System.Object[] 
     -680,000  -10,000     10,000   0    680,000     0      68        System.Data.DataRow 
     -7,514   -88      20,273   20,185   749,040     741,526     36    36    System.String 
     -918,294  -20,392     60,734   40,342   1,932,650    1,014,356            Managed Heap(Allocated)! 
     -917,472  0      0    0    1,954,980    1,037,508            Managed Heap(TotalSize) 

这说明我们有917KB的内存与数据表的方式分配和10K DataRow的实例是仍然在托管堆上游荡。但这些数字是否正确?

因为大多数的对象是已经死了,但没有完整的GC没有发生之前,我们确实需要一个内存转储这些对象仍然报告为活着。解决方法是告诉MemAnalyzer考虑只有扎根(活)对象,如WinDBG的做它用-live选项:

C>MemAnalyzer.exe -f DataTable.dmp -pid2 20792 -dts 5 -live 
Delta(Bytes) Delta(Instances)  Instances  Instances2  Allocated(Bytes)  Allocated2(Bytes)  AvgSize(Bytes) AvgSize2(Bytes) Type 
-68,000   -1,000     1,000   0    68,000     0      68        System.Data.DataRow 
-36,960   -8      8    0    36,960     0      4620       System.Data.RBTree+Node<System.Data.DataRow>[] 
-16,564   -5      10    5    34,140     17,576     3414   3515   System.Object[] 
-4,120   -2      2    0    4,120     0      2060       System.Data.DataRow[] 
-4,104   -1      19    18    4,716     612      248    34    System.String[] 
-141,056  -1,285     1,576   291    169,898     28,842             Managed Heap(Allocated)! 
-917,472  0      0    0    1,954,980    1,037,508            Managed Heap(TotalSize) 

的DataTable的方法仍然需要,因为额外的DataRow 141056个字节的内存,对象[]和System.Data.RBTree + Node []实例。仅测量工作集是不够的,因为托管堆已被取消分配。如果GC认为下一个内存峰值不远,GC可以保留大量内存。因此,测量提交的内存几乎是毫无意义的度量标准,除非您的(非常低的)目标是仅修复GB内存泄漏。

来衡量事物的正确方法是测量

  • 非托管的总和堆
  • 分配托管堆
  • 内存映射文件
  • 页面文件出炉的内存映射文件(可共享内存)
  • Private Bytes

这实际上正是MemAnalyzer用-vmmap开关所做的,它在Sysinternals的路径中对vmmap进行了预期。

MemAnalyzer -pid ddd -vmmap 

这样,您还可以跟踪非托管内存泄漏或文件映射泄漏。 MemAnalyzer的返回值是以KB为单位的总分配内存。

  • 如果使用-vmmap,它将报告上述点的总和。
  • 如果vmmap不存在,它将只报告分配的托管堆。
  • 如果添加了-live,则仅报告有根管理的对象。

我的确编写了这个工具,因为我的知识中没有任何工具可以让我们以整体的方式查看内存泄漏。我总是想知道是否泄漏内存,无论它是否管理,非托管或其他。

enter image description here

通过写diff输出的一个CSV文件,您可以轻松地创建透视差异图表类似上面。

MemAnalyzer.exe -f DataTable.dmp -pid2 20792 -live -o ExcelDiff.csv 

这应该给你一些想法,如何以更准确的方式跟踪分配指标。

+0

如果我让它运行几天,我可以轻松地吃掉一个GB的RAM ......我在VS2017上,所以它比以前的版本有更多的内存分析工具。 – DaBlue

+0

内存转储的托管内存分析只是Ultimate Edition的一部分。是的,VS已经变好,但仍然不好。 PerfView要好得多,但你需要小心其概率抽样方法,这可能会使数字偏离很多。如果你比较VS和MemAnalyzer的编号,你会发现VS的工作方式就像MemAnalyzer没有-live开关,这使得很难检查你现在是否有更多或更少的memore分配,如果你尝试优化的东西。当然你可以触发GC,但这仍然是一种非常手动的方法。 –