从大对象(特别是数据集)中回收内存

时间:2014-02-15 04:32:37

标签: c# windows-services garbage-collection dataset

我继承了一项Windows服务,该服务通过远程处理接受请求,以分析数据库中潜在的大量数据。它通过将原始数据加载到数据集中,将数据解析为树状结构的对象,然后运行分析来检索原始数据。

我遇到的问题是,如果分析了一大堆数据,那么即使我积极地强制进行垃圾收集,也不会在分析完成后将所有内存都返回给系统。例如,如果分析了500MB的数据集,那么windows服务从~10MB(启动时的基线)到~500MB,然后在GC.Collect()之后下降到~200MB,并且永远不会降低,甚至一夜之间。恢复内存的唯一方法是停止并重新启动服务。但如果我进行一个小分析,服务从大约10MB到大约50MB,然后降到大约20MB。也不是很好,但在分析完成后,大小数据之间的最终利用率之间存在巨大差异。

这本身并不是内存泄漏,因为如果我反复运行大分析,每次完成时总内存会减少到大约200MB。

这是一个问题,因为Windows服务在共享服务器上运行,我不能让我的进程一直占用大量内存。如果它达到峰值然后在分析完成后再回落,那就没关系,但这种情况会出现并且部分下降到一个不可接受的高数字。典型的情况是运行分析,然后闲置几个小时。

不幸的是,这个代码库非常庞大,其中很大一部分被编码为使用专有数据访问层返回的数据表,因此使用备用方法加载数据不是一个选项(我希望我可以,加载将所有数据存入内存只是为了循环它是没有意义的。)

所以我的问题是:

1)为什么运行大型数据集会导致内存利用率降低到~200MB,但运行小型数据集会导致内存利用率降低到~20MB?它显然是以某种方式挂在数据集的各个部分上,我只是看不到它。

2)如果我循环遍历数据表的行,为什么会有所不同?(见下文)?

3)如何在分析完成后将内存恢复到合理的水平,而不会从根本上改变架构?

我创建了一个小型Windows服务/客户端应用程序来重现问题。我使用的测试数据库有一个包含一百万条记录的表,一个int PK和两个字符串字段。这是我尝试过的场景 - 客户端(控制台应用程序)通过远程循环远程调用LoadData十次。

1)doWork = true,garbageCollect = true,recordCount = 100,000。内存高达78MB,然后稳定在22MB。

2)doWork = false,garbageCollect = true,recordCount = 100,000。内存高达78MB,稳定在19MB。说真的,在没有做任何事情的情况下,还要多行3MB才能完成这些行吗?

3)doWork = false,garbageCollect = false,recordCount = 100,000。内存高达约178MB,然后稳定在78MB。强制垃圾收集显然正在做些什么,但还不足以满足我的需求。

4)doWork = false,garbageCollect = true,recordCount = 1,000,000。内存高达500MB,稳定在35MB。当数据集较大时,为什么它会稳定在更高的数字?

5)doWork = false,garbageCollect = true,recordCount = 1,000。它运行速度太快,无法看到峰值,但稳定在12MB左右。

public string LoadData(bool doWork, bool garbageCollect, int recordCount)
{
    var dataSet = new DataSet();

    using (var sqlConnection = new SqlConnection("...blah..."))
    {
        sqlConnection.Open();

        using (var dbCommand = sqlConnection.CreateCommand())
        {
            dbCommand.CommandText = string.Format("select top {0} * from dbo.FakeData", recordCount.ToString());
            dbCommand.CommandType = CommandType.Text;

            using (var dbReader = new SqlDataAdapter(dbCommand))
            {
                dbReader.Fill(dataSet);
            }                    
        }

        sqlConnection.Close();
    }

    // loop over the records

    var count = dataSet.Tables[0].Rows.Count;

    if (doWork)
    {
        foreach (DataRow row in dataSet.Tables[0].Rows) {}
    }

    dataSet.Clear();
    dataSet = null;

    if (garbageCollect)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
    }

    return string.Format("Record count is {0}", count);
}

0 个答案:

没有答案