DataTable不释放内存

mha*_*125 9 c# memory datatable memory-management

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

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

  1. DataTable.Clear()然后调用DataTable.Dispose()(释放大约800 MB内存,但每次数据加载作业完成后仍会增加200 MB内存,在数据加载3或4次后,内存异常因为超过1.5 G而抛出异常记忆总计)
  2. 将DataTable设置为null(没有释放内存,如果选择加载更多数据,则抛出内存不足异常)
  3. 直接调用DataTable.Dispose()(没有释放内存,如果选择加载更多数据,则抛出内存不足异常)

以下是我尝试进行测试的代码(在实际程序中,它不是递归调用的,它是由一些目录监视逻辑触发的.这段代码仅用于测试.很抱歉混淆.):

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();
        }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

Sco*_*ain 5

您的主要问题是垃圾收集器的行为会有所不同,具体取决于您是在调试还是在没有调试器的发布模式下。

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

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

GC 在“可调试情况”期间更改其行为的原因是将调试器本身视为对当前执行代码范围内的所有变量的引用。如果没有,您将无法在监视窗口中查看变量的值或将鼠标悬停在它上面。因此,在变量超出范围或覆盖变量之前,您不能“传递对对象的最后一个引用”。

有关该过程的更多详细信息请参阅关于垃圾收集、范围和对象生命周期的博客文章。


mha*_*125 2

最终我发现这个数据表未释放内存的bug是由Oracle批量复制引起的。以防万一有人遇到同样的问题。请参阅以下帖子以供参考

OracleBulkCopy内存泄漏(OutOfMemory异常)