Страницы

Поиск по вопросам

понедельник, 2 декабря 2019 г.

Сборщик мусора c# не очищает память

#c_sharp


Есть статичный метод

private static bool GetXml(string date)
{
    try
    {
        //Подключаемся к сервису
        KBODataProviderClient client = new KBODataProviderClient();
        //Получаем данные
        string[] result = client.getKBOContracts("2018-12-25", null);

        //Если данных нет
        if (result == null || result.Length <= 0)
        {
            log.InfoFormat("[{0}]|Pbo return 0 rows", runGuid);
            return false;
        }

        if (File.Exists(filePath))
            File.Delete(filePath);

        //Сохраняем XML
        using (StreamWriter sw = new StreamWriter(filePath, true, Encoding.UTF8))
        {
            //пишем первую запись с заголовков
            sw.WriteLine("");
            sw.WriteLine("");
            for (int i = 0; i < result.Length; i++)
            {
                //удаляем заголовок
                string temp = result[i].Replace("", "");
                sw.WriteLine(temp);
            }
            sw.WriteLine("");
        }


        log.InfoFormat("[{0}]|Create XML success", runGuid);

        return true;

    }
    catch (Exception ex)
    {
        log.ErrorFormat("[{0}]|GetXml error {1}", runGuid, ex + Environment.NewLine
+ ex.StackTrace);
        return false;
    }
}


При вызове метода он подключается к WCF сервису, оттуда приходит массив строк (возвращает
string[]), который я потом пишу в файлик.
Проблема в том, что при получении данных разумеется съедается память, и после завершения
метода память не очищается.

Собственно, я знаю то, что переменная string[] result является временной переменной
и после завершения метода она должна собраться сборщиком мусора, но этого не происходит
(данные занимают около 400мб в памяти)

Первым, что пришло в голову это проблема из-за статичного метода. Тогда я решил потереть
ссылку

result = null;
GC.Collect();


и вызвать сборщик самому. Но и тут эффекта 0.
В чем проблема?

upd:

Использование метода идет так:

if (GetXml(date))
{
    //парсинг
}


сам метод дополнил так:

private static bool GetXml(string date)
{
    try
    {
        //Подключаемся к сервису
        KBODataProviderClient client = new KBODataProviderClient();
        //Получаем данные
        string[] result = client.getKBOContracts("2018-12-25", null);

        //Если данных нет
        if (result == null || result.Length <= 0)
        {
            log.InfoFormat("[{0}]|Pbo return 0 rows", runGuid);
            return false;
        }

        if (File.Exists(filePath))
            File.Delete(filePath);

        //Сохраняем XML
        using (StreamWriter sw = new StreamWriter(filePath, true, Encoding.UTF8))
        {
            //пишем первую запись с заголовков
            sw.WriteLine("");
            sw.WriteLine("");
            for (int i = 0; i < result.Length; i++)
            {
                //удаляем заголовок
                string temp = result[i].Replace("", "");
                sw.WriteLine(temp);
            }
            sw.WriteLine("");
        }

        log.InfoFormat("[{0}]|Create XML success", runGuid);

        client.Close();
        client = null;

        result = null;

        GC.Collect();

        return true;

    }
    catch (Exception ex)
    {
        log.ErrorFormat("[{0}]|GetXml error {1}", runGuid, ex + Environment.NewLine
+ ex.StackTrace);
        return false;
    }
}


Клиент сервиса я закрываю и на всякий даже null присваиваю. Далее присваиваю null
строке и вызываю сборщик.

В итоге при получении данных память увеличивается до 320мб(+- офк) и после завершения
падает до +-211 и продолжает работать.

Метод парсинга не занимает столько памяти, т.к без получения данных при парсинге
память остается на уровне +-60мб.


попробовал Array.Clear, так-же 0 эффекта (скрин выше)
    


Ответы

Ответ 1



Проблема решена. Я решил проверить все дело на левых проектах потихоньку перенося код и оказалось чудом, когда на новом проекте память очищалась нормально(требовалось лишь вызвать GC.Collect()) Первым делом я решил проверить версии .net, на проекте где все работало был 4.7.1, но изменение версии не помогло. Тогда я начал в буквальном смысле добавлять по строчке и сверять код. Причина оказалось в app.config, а именно в секции которую генерирует VS при добавлении ссылки на сервис (там указываются таймауты и прочее). Конкретно проблема была в том, что таймауты (CloseTimeout, OpenTimeout, ReceiveTimeout, SendTimeout) указывались в привязке (секция basicHttpBinding). Если я указывал эти таймауты в этой секции - память не очищалась (ни в основном, ни в каких других проектах), т.е если делал так: но если я указывал таймауты через код: client.Endpoint.Binding.CloseTimeout = new TimeSpan(0, 30, 0); client.Endpoint.Binding.OpenTimeout = new TimeSpan(0, 30, 0); client.Endpoint.Binding.ReceiveTimeout = new TimeSpan(0, 30, 0); client.Endpoint.Binding.SendTimeout = new TimeSpan(0, 30, 0); то сборщик мусора успешно собирал мусор. Чтобы сделать настройку времени из конфига я воспользовался ConfigurationManager тогда в коде я делаю так: //Получаем время, переводим его в TimeSpan (если указать их в конфиге, то память не будет очищена) TimeSpan time = new TimeSpan(TimeSpan.TicksPerMinute * int.Parse(ConfigurationManager.AppSettings.Get("timeout"))); client.Endpoint.Binding.CloseTimeout = time; client.Endpoint.Binding.OpenTimeout = time; client.Endpoint.Binding.ReceiveTimeout = time; client.Endpoint.Binding.SendTimeout = time; и сборщик мусора так-же успешно очищает память

Ответ 2



Все языки, использующие сборщик мусора, известны своей нелюбовью возвращать память системе. Проще всего с этим смириться: как правило, неиспользуемая память быстро оседает в файле подкачки и никому там не мешает. Проблемы начинаются только когда уже и файл подкачки заканчивается. Однако, есть несколько способов, позволяющих принудительно освободить память. Способ 1 - отдельный процесс. Можно вынести вызов GetXml в отдельную программу, которая сделает всю тяжелую по памяти работу и завершится. Это - самый медленный способ (он медленнее даже чем ничего не делать), но он позволяет быстро решить проблему с нехваткой памяти. Способ 2 - поточная обработка. Этот способ требует переписывания клиента так, чтобы он не возвращал не список строк, а работал с потоками ввода-вывода. Идея в том, чтобы таскать всюду за собой Stream (или PipeReader, см. библиотеку System.Threading.Pipelines), и никогда не хранить в памяти более, к примеру, четырех килобайтов данных. Достоинство этого способа - очень низкое потребление памяти если сделать всё правильно. Недостаток же - в том, что такие операции, как "разделить поток на две части" или "выкусить декларацию XML из начала документа", требуют сложных преобразований архитектуры. Так, к примеру, вполне возможно что вам не удастся получить список потоков от клиента, и вместо этого придется подписываться на событие. Это может выглядеть как-то так: using (StreamWriter sw = new StreamWriter(filePath, true, Encoding.UTF8)) { sw.WriteLine(""); sw.WriteLine(""); var client = new KBODataProviderClient(); client.ProcessResult += stream => { sw.Flush(); stream.Position += Encoding.UTF8.GetByteCount(""; stream.CopyTo(sw.BaseStream); }; client.getKBOContracts("2018-12-25", null); sw.WriteLine(""); } Способ 3 - неуправляемая память Этот способ также требует переписывания клиента. Он может оказаться как проще поточной обработки (за счет более простых алгоритмов), так и сложнее (за счет более сложного API). Недостатком этого способа является непереносимость, так как он завязывается на конкретное API платформы. Нужно выделить кусок неуправляемой памяти через P/Invoke (лучше всего использовать пару функций VirtualAlloc/VirtualFree - они уж точно вернут память системе), и (старых подход) либо обернуть ее в SafeBuffer и дальше в UnmanagedMemoryAccessor + UnmanagedMemoryStream, либо (новый подход) обернуть ее в MemoryManager из библиотеки System.Memory и далее работать с Memory

Ответ 3



Поведение сборщика мусора недетерминировано и как следствие он запускается тогда, когда считает нужным. Это сделано в целях оптимизации, так как сборка мусора- это ресурсоемкая операция: Надо найти объекты без ссылок Затем освободить память Выполнить сжатие(дефрагментацию) кучи В вашем же случае явного вызова GC.Collect(); память все таки должна очищаться, если вы работаете с управляемыми ресурсами и нету никаких утечек. Память может не очищаться из-за того, что программа запущена в режиме отладки и в этом режиме переменным проливается жизнь, что логично, ведь мусор может собираться еще до выхода из метода, если видно, что переменная не видна, но во время отладки мы можем наблюдать за переменными и как следствии было бы неудобно, если бы из Watch'ей переменные пропадали во время отладки. Попробуйте запустить в режиме релиза со всевозможными оптимизациями. Подробнее об этом написано тут. Так же, вроде, Рихетр писал, что сборщик мусора может не сразу отдавать память системе, так как может предполагать, что она понадобится в ближайшее время.

Комментариев нет:

Отправить комментарий