我正在使用数据表以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);
}
因此消息将从数据表中删除,但内存似乎没有被释放。我希望释放内存......?
或许还有一种更好的方法来使用数据表之外的其他东西来运行日志?
答案 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的内存泄漏。
衡量事物的正确方法是衡量
的总和这实际上就是MemAnalyzer对-vmmap开关所做的事情,它在路径中将vmmap从Sysinternals中取消。
MemAnalyzer -pid ddd -vmmap
通过这种方式,您还可以跟踪非托管内存泄漏或文件映射泄漏。 MemAnalyzer的返回值是以KB为单位的总分配内存。
我确实编写了这个工具,因为根据我的知识,没有任何工具可以很容易地以整体的方式查看内存泄漏。我总是想知道我是否泄漏了内存,无论是管理,不管理还是别的。
通过将diff输出写入CSV文件,您可以轻松创建像上面那样的Pivot diff图表。
MemAnalyzer.exe -f DataTable.dmp -pid2 20792 -live -o ExcelDiff.csv
这应该会为您提供一些如何以更准确的方式跟踪分配指标的想法。