DataTable不释放内存

时间:2015-06-04 14:57:26

标签: c# memory memory-management datatable

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

我尝试了3种释放记忆的方法:

  1. DataTable.Clear()然后调用DataTable.Dispose()(释放大约800 MB内存,但每次数据加载作业完成后仍会增加200 MB内存,在数据加载3或4次后,内存异常抛出,因为它总共超过1.5 G内存)
  2. 将DataTable设置为null(未释放内存,如果选择加载更多数据,则抛出内存不足异常)
  3. 直接调用DataTable.Dispose()(没有释放内存,如果选择加载更多数据,则抛出内存不足异常)
  4. 以下是我尝试进行测试的代码(在实际程序中,它不是递归调用的,它是由一些目录监视逻辑触发的。这段代码仅用于测试。很抱歉混淆。):

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

4 个答案:

答案 0 :(得分:3)

如果在要求在函数外重复时移动零件,则正确释放内存(仅测试方法1(清除和处理)):

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

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

答案 1 :(得分:3)

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

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

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

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

有关此流程的详细信息,请参阅博客文章On Garbage Collection, Scope and Object Lifetimes

答案 2 :(得分:1)

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

Cleaning up variables in methods

避免OutOfMemory异常的一种方法是使用MemoryFailPoint类,它允许您设置一个失败点,超过该类别会抛出InsufficientMemoryException,这样您就有机会放慢速度在此过程中,直到另一个工作线程可用。我不确定这是否是您想要尝试的内容,但您可以使用它:

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

答案 3 :(得分:1)

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

OracleBulkCopy Memory Leak(OutOfMemory Exception)