DataTable не освобождает память

У меня есть процесс загрузки данных, который загружает большой объем данных в DataTable, а затем выполняет некоторый процесс данных, но каждый раз, когда задание завершает DataLoader.exe (32bit, имеет ограничение памяти 1.5 G) не освобождает всю используемую память.

Я пробовал 3 способа освободить память:

  1. DataTable.Clear () затем вызовите DataTable.Dispose () (освободите около 800 МБ памяти, но все равно увеличивайте 200 МБ памяти каждый раз, когда задание загрузки данных завершается, после 3 или 4 раз данных загрузка, из памяти исключение выброшено, потому что оно превышает 1,5 г памяти в общей сложности)
  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();
        }
    }
  }
}

4 ответов


Если вы перемещаете часть, когда вы просите повторить вне функции, память освобождается правильно (только проверенный метод 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();
    }
}

вероятно, память объектов освобождается, когда они находятся вне области


ваша основная проблема заключается в том, что поведение сборщика мусора отличается в зависимости от того, отлаживаете ли вы или в режиме выпуска без отладчика.

когда в отладочной сборке или сборке выпуска с отладчиком присутствуют все объекты, их время жизни продлевается до всего срока службы метода. Это означает table не может быть восстановлен GC, пока вы не завершите LoadData метод. Вот почему у тебя кончается память.

Если вы измените свою программу в режиме выпуска и запустите ее без отладчика, а затем, как только вы передадите последнюю ссылку на объект переменной table указывает на то, что в вашем пути кода объект получает право на сборку мусора, и вы освобождаете память.

причина, по которой GC изменяет свое поведение во время "отладочной ситуации", заключается в том, что сам отладчик содержит ссылку на все переменные, которые находятся в области текущего исполняемого кода. Если это не вы не сможете посмотреть на значение переменной в окне просмотра или наведите на нее курсор. Из-за этого вы не можете "передать последнюю ссылку на объект", пока переменная не выйдет из области видимости или вы не перезапишете переменную.

посмотреть в блоге на сборке мусора, области и времени жизни объекта для получения более подробной информации о процессе.


на самом деле нет способа заставить C# освободить память, как вы бы с кодом, который не имеет управления памятью. Это помогает понять, как работает сборщик мусора .NET. В основном использование памяти в приложениях .NET поднимается до одного из трех условий, которые запускают сборку мусора. Я описываю процесс в ответе на следующий вопрос:

очистка переменных в методах

один способ избежать OutOfMemory исключение использовать MemoryFailPoint класс, который позволяет установить точку сбоя, за которой InsufficientMemoryException выбрасывается, давая вам возможность замедлить процесс, пока не будет доступен другой рабочий поток. Я не уверен, что это то, что вы хотели бы попробовать, но это доступно для вас:

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


В конце концов я обнаружил, что эта таблица данных не освобождает ошибку памяти, вызванную массовым копированием Oracle. На случай, если у кого-то возникнет такая же проблема. Пожалуйста, смотрите следующий пост для справки

Утечка Памяти OracleBulkCopy (Исключение OutOfMemory)