DataTable.Row.Delete内存泄漏

时间:2017-05-30 16:17:46

标签: c#

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

样品:

 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);
 }    

因此消息将从数据表中删除,但内存似乎没有被释放。我希望释放内存......?

或许还有一种更好的方法来使用数据表之外的其他东西来运行日志?

3 个答案:

答案 0 :(得分: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();
        }   
    }
}

使用DataTable方法运行一次,使用Queue方法运行一次。

每次查看内存使用情况.net小提琴报告:

DataTable内存: 2.74Mb

队列记忆: 1.46Mb

几乎是内存使用量的一半!我们所做的只是停止使用DataTables。

.Net DataTables众所周知是内存饥渴。他们有相当好的理由,他们可以存储大量复杂的架构信息,并可以跟踪更改等。

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

没有?转储DT,使用System.Collections(.Generic)下的内容。

答案 1 :(得分:-1)

每当您修改/删除DataTable中的行时,DataTable仍会保留旧/已删除的数据,直到您致电DataTable.AcceptChanges

  

调用AcceptChanges时,任何仍处于编辑模式的DataRow对象都会成功结束其编辑。 DataRowState也会更改:所有已添加和已修改的行都将变为未更改,并删除已删除的行。

没有内存泄漏,因为它是按设计的。

作为替代方案,您可以使用比队列更适合的circular buffer

答案 2 :(得分:-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)

这表明我们使用DataTable方法分配了917KB的内存,并且10K DataRow实例仍然在托管堆上浮动。但这些数字是否正确?

没有

因为大多数对象已经死了但在我们确实进行内存转储之前没有完全GC发生这些对象仍然被报告为活着。修复是告诉MemAnalyzer只考虑像Windbg这样的root(live)对象使用-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)

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

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

的总和
  • Unmanaged Heap
  • 已分配托管堆
  • 内存映射文件
  • 页面文件烘焙内存映射文件(可共享内存)
  • 私人字节

这实际上就是MemAnalyzer对-vmmap开关所做的事情,它在路径中将vmmap从Sysinternals中取消。

MemAnalyzer -pid ddd -vmmap 

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

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

我确实编写了这个工具,因为根据我的知识,没有任何工具可以很容易地以整体的方式查看内存泄漏。我总是想知道我是否泄漏了内存,无论是管理,不管理还是别的。

enter image description here

通过将diff输出写入CSV文件,您可以轻松创建像上面那样的Pivot diff图表。

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

这应该会为您提供一些如何以更准确的方式跟踪分配指标的想法。