DataTable不释放内存

DataTable does not release memory

我有一个数据加载过程,将大量数据加载到 DataTable 然后进行一些数据处理,但每次作业完成时 DataLoader.exe(32 位,有 1.5G 内存限制)不释放所有正在使用的内存。

我尝试了3种释放内存的方法:

  1. DataTable.Clear() 然后调用DataTable.Dispose() (释放大约800 MB内存,但每次数据加载作业完成仍增加200 MB内存,数据加载3或4次后,out总共超过1.5G内存抛出的内存异常)
  2. 设置DataTable为空(不释放内存,如果选择加载更多数据,抛出内存不足异常)
  3. 直接调用DataTable.Dispose()(不释放内存,如果选择load more data,会抛出out of memory异常)

以下是我尝试测试的代码(在实际程序中它不是递归调用的,它是由一些目录监视逻辑触发的。这段代码只是为了测试。抱歉造成混淆。):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

namespace DataTable_Memory_test
{
class Program
{
    static void Main(string[] args)
    {
        try
        {
            LoadData();                
            Console.ReadKey();

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
            Console.ReadKey();
        }
    }

    private static void LoadData()
    {
        DataTable table = new DataTable();
        table.Columns.Add("Dosage", typeof(int));
        table.Columns.Add("Drug", typeof(string));
        table.Columns.Add("Patient", typeof(string));
        table.Columns.Add("Date", typeof(DateTime));

        // Fill the data table to make it take about 1 G memory.
        for (int i = 0; i < 1677700; i++)
        {
            table.Rows.Add(25, "Indocin", "David", DateTime.Now);
            table.Rows.Add(50, "Enebrel", "Sam", DateTime.Now);
            table.Rows.Add(10, "Hydralazine", "Christoff", DateTime.Now);
            table.Rows.Add(21, "Combivent", "Janet", DateTime.Now);
            table.Rows.Add(100, "Dilantin", "Melanie", DateTime.Now);
        }
        Console.WriteLine("Data table load finish: please check memory.");
        Console.WriteLine("Press 0 to clear and dispose datatable, press 1 to set datatable to null, press 2 to dispose datatable directly");
        string key = Console.ReadLine();
        if (key == "0")
        {
            table.Clear();
            table.Dispose();
            Console.WriteLine("Datatable disposed, data table row count is {0}", table.Rows.Count);
            GC.Collect();   
            long lMemoryMB = GC.GetTotalMemory(true/* true = Collect garbage before measuring */) / 1024 / 1024; // memory in megabytes
            Console.WriteLine(lMemoryMB);

        }
        else if (key == "1")
        {
            table = null;
            GC.Collect();
            long lMemoryMB = GC.GetTotalMemory(true/* true = Collect garbage before measuring */) / 1024 / 1024; // memory in megabytes
            Console.WriteLine(lMemoryMB);
        }
        else if (key == "2")
        {
            table.Dispose();
            GC.Collect();
            long lMemoryMB = GC.GetTotalMemory(true/* true = Collect garbage before measuring */) / 1024 / 1024; // memory in megabytes
            Console.WriteLine(lMemoryMB);
        }
        Console.WriteLine("Job finish, please check memory");
        Console.WriteLine("Press 0 to exit, press 1 to load more data and check if throw out of memory exception");
         key = Console.ReadLine();
        if (key == "0")
        {
            Environment.Exit(0);
        }
        else if (key == "1")
        {
            LoadData();
        }
    }
  }
}

没有真正的方法可以像使用没有内存管理的代码那样强制 C# 释放内存。它有助于理解 .NET 垃圾收集器的工作原理。基本上,.NET 应用程序中的内存使用量上升到触发垃圾回收的三个条件之一。我在回答以下问题时描述了这个过程:

Cleaning up variables in methods

避免 OutOfMemory 异常的一种方法是利用 MemoryFailPoint class,它允许您设置一个失败点,超过该点将抛出 InsufficientMemoryException,让您有机会减慢进程,直到另一个工作线程可用。我不确定这是否是您想要尝试的,但您可以使用它:

https://msdn.microsoft.com/en-us/library/system.runtime.memoryfailpoint%28v=vs.100%29.aspx?f=255&MSPPError=-2147217396

如果在函数外请求reiteate时移动了部分,内存被正确释放(仅测试方法1(Clear and Dispose)):

static void Main(string[] args)
{
    try
    {
        string key;
        do
        {
            LoadData();
            Console.WriteLine("Job finish, please check memory");
            Console.WriteLine("Press 0 to exit, press 1 to load more data and check if throw out of memory exception");
            key = Console.ReadLine();
        } while (key == "1");
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
        Console.ReadKey();
    }
}

可能,对象的内存在超出范围时被释放

你的主要问题是垃圾收集器的行为是不同的,这取决于你是在调试还是在没有调试器的情况下处于发布模式。

在调试版本或带有调试器的发布版本中,所有对象的生命周期都会延长到方法的整个生命周期。这意味着在您完成 LoadData 方法之前,GC 无法回收 table。这就是为什么 运行ning 内存不足的原因。

如果您将程序更改为发布模式并且 运行 它没有调试器,那么一旦您传递对对象的最后一个引用,变量 table 就会指向您的代码路径中对象有资格进行垃圾回收,您将释放内存。

GC 在 "debuggable situation" 期间改变其行为的原因是将调试器本身视为对当前执行代码范围内的所有变量的引用。如果没有,您将无法在手表 window 中查看变量的值或将鼠标悬停在它上面。因此,在变量超出范围或覆盖变量之前,您不能 "pass the last reference to the object"。

有关该过程的更多详细信息,请参阅博文 On Garbage Collection, Scope and Object Lifetimes

最终我发现这个数据 table not release memory bug 是由 Oracle bulk copy 引起的。以防万一有人遇到同样的问题。请参考以下post