C# не освобождает память после завершения задачи

следующий код является упрощенным примером проблемы, которую я вижу. Это приложение потребляет около 4 ГБ памяти, прежде чем выбрасывать исключение, поскольку словарь слишком большой.

 class Program
 {
    static void Main(string[] args)
    {
        Program program = new Program();

        while(true)
        {
            program.Method();
            Console.ReadLine();
        }
    }

    public void Method()
    {
        WasteOfMemory memory = new WasteOfMemory();
        Task tast = new Task(memory.WasteMemory);
        tast.Start();
    }


}

public class WasteOfMemory
{
     public void WasteMemory()
     {
         Dictionary<string, string> aMassiveList = new Dictionary<string, string>();

         try
         {
             long i = 0;
             while (true)
             {
                 aMassiveList.Add(i.ToString(), "I am a line of text designed to waste space.... I am exceptionally useful........");
                 i++;
             }

         }
         catch(Exception e)
         {
             Console.WriteLine("I have broken myself");
         }
     }
}

Это все, как ожидалось, хотя то, что мы не можем в настоящее время решить, когда эта память должна быть освобождена из среды CLR.

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

однако и еще более запутанным, если мы подождем, пока задача не будет завершена, а затем нажмите enter, чтобы запустить задачу снова освобождается память, поэтому, очевидно, предыдущий словарь был собран мусор (не так ли?).

Итак, почему память не освобождается? И как мы можем заставить CLR освободить память?

любые объяснения или решения, будет принята оцененный.

EDIT: после ответов, особенно Beska, очевидно, что мое описание проблемы не является самым ясным, поэтому я попытаюсь уточнить.

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

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

в Примере словарь заполняется до предела словаря, а затем выдает исключение, оно не продолжает заполняться навсегда! Это задолго до того, как наша память заполнена, и это не вызывает исключения OutOfMemoryException. Следовательно, результатом является большой объект в памяти, а затем задача завершается.

на этом этапе мы ожидаем, что словарь будет вне области действия, поскольку и задача, и метод "Method" завершены. Следовательно, мы ожидаем, что словарь для сбора мусора и восстановления памяти. На самом деле память не освобождается, пока снова не вызывается "метод", создавая новый экземпляр WasteOfMemory и начиная новую задачу.

надеюсь, что это прояснит проблему немного

6 ответов


хорошо, я следил за этим...Я думаю, что есть пара вопросов, некоторые из которых люди затронули, но я думаю, не отвечая на настоящий вопрос (который, по общему признанию, занял некоторое время, чтобы признать, и я не уверен, что отвечаю на то, что вы хотите даже сейчас.)

Это все, как ожидалось, хотя то, что мы не можем в настоящее время выяснить, когда эта память должна быть освобождена из среды CLR.

Как говорили другие, пока задача выполняется, словарь не будет выпущен. Его используют. Он становится больше, пока у тебя не кончается память. Я уверен, что вы это понимаете.

мы позволили задаче завершить, а затем смоделировали ситуацию перегрузки памяти, но память, потребляемая словарем, не освобождается. Поскольку в ОС заканчивается память, не оказывает ли она давление на CLR, чтобы освободить память?

вот, я думаю, настоящий вопрос.

Если я вас понимаю правильно, вы говорите, что настроили это, чтобы заполнить память. И затем, после его сбоя (но до того, как вы нажмете return, чтобы начать новую задачу), вы пытаетесь других вещей вне этой программы, такие как запуск других программ в Windows чтобы попытаться получить GC для сбора памяти, верно? Надеясь, что ОС поговорит с GC и начнет давить на него, чтобы сделать это.

однако и еще более запутанным, если мы подождем, пока задача не будет завершена, а затем нажмите введите, чтобы снова запустить задачу память освобождается, поэтому очевидно, что предыдущий словарь был собран мусор (не так ли?).

Я думаю, вы ответили на свой вопрос...он не был обязательно выпущен, пока вы не нажмете return, чтобы начать новую задачу. Новая задача нуждается в памяти, поэтому она переходит в GC, и GC радостно собирает память из предыдущей задачи, которая теперь закончилась (после выбрасывания из полной памяти).

Итак, почему память не отпускают? И как мы можем заставить CLR освободить память?

Я не знаю, что вы можете заставить GC освободить память. Вообще говоря, он делает это, когда хочет (хотя некоторые типы хакеров могут знать какой-то скользкий способ заставить его руку.) Конечно, .NET решает, когда запускать GC, и поскольку ничего не происходит, пока программа просто сидит там, она вполне может решить, что ей это не нужно. Что касается того, может ли ОС оказывать давление на GC для запуска, кажется из ваших тестов ответ "нет". Возможно, это противоречит интуиции.

Это ты намекаешь?


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

(1) ваша программа работает бесконечно без прекращения и

(2) вы никогда не меняете указатель на свой словарь, поэтому у GC нет причин касаться словаря.

Так что для меня ваша программа делает ровно что он должен делать.


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

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

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


GC будет выпускать только неподтвержденные объекты, так как словарь ссылается на вашу программу, он не может быть выпущен GC


Как вы написали метод WasteMemory, он никогда не выйдет (если переменная " i " не переполнится, чего не произойдет в этом году) и ПОТОМУ ЧТО ОН НИКОГДА НЕ ВЫЙДЕТ он будет держать ПРИ ИСПОЛЬЗОВАНИИ ссылка на внутренний словарь.

Даниэль Уайт прав, вы должны прочитать о том, как работает GC.

Если ссылки используются, GC не будет собирать ссылочную память. в противном случае, как бы любая программа работа?

Я не понимаю, чего вы ожидаете от CLR/GC. Нечего мусор собирать в одном из способ WasteMemory.

однако и еще более запутанным, если мы подождем, пока задача не будет завершена, а затем нажмите enter, чтобы запустить задачу снова освобождается память, поэтому, очевидно, предыдущий словарь был собран мусор (не так ли?).

при нажатии Enter создается и запускается новая задача. Это не та же задача, это новая задача - новый объект, содержащий ссылку на новый экземпляр WasteOfMemory.

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

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


измените свой метод на оператор using

пример:

Using (WateOfMemory memory = new WateOfMemory())
{
    Task tast = new Task(memory.WasteMemory); 
    tast.Start();
 }

и добавьте disposible WateOfMemoryClass (кстати, ваш конструктор-WasteOfMemory)

#region Dispose
    private IntPtr handle;
    private Component component = new Component();
    private bool disposed = false;

    public WateOfMemory()
    {

    }

    public WateOfMemory(IntPtr handle)
    {
        this.handle = handle;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if(!this.disposed)
        {
            if(disposing)
            {
            component.Dispose();
            }

            CloseHandle(handle);
            handle = IntPtr.Zero;            
        }
        disposed = true;         
    }

    [System.Runtime.InteropServices.DllImport("Kernel32")]
    private extern static Boolean CloseHandle(IntPtr handle);

    ~WateOfMemory()      
    {
        Dispose(false);
    }
    #endregion