Подтвердить что ты не робот

Сбор мусора не происходит даже при необходимости

Я сделал 64-битное тестовое приложение WPF. Когда мое приложение работает и открывается диспетчер задач, я просматриваю использование своей системной памяти. Я вижу, что я использую 2 ГБ, и у меня есть 6 ГБ.

В моем приложении я нажимаю кнопку "Добавить", чтобы добавить новый массив байтов размером 1 ГБ в список. Я вижу, что использование моей системной памяти увеличивается на 1 ГБ. Я нажимаю "Добавить" всего 6 раз, заполняя 6 ГБ памяти, которую я имел, когда я начал.

Я нажимаю кнопку "Удалить" 6 раз, чтобы удалить каждый массив из списка. Удаленные байт-массивы не должны ссылаться на какой-либо другой объект в моем элементе управления.

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

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

Как проверка работоспособности, у меня есть кнопка для принудительного включения GC. Когда я нажимаю это, я быстро возвращаю 6 ГБ. Разве это не доказывает, что мои 6 массивов не были указаны и МОГУТ быть собраны, если бы GC знал/хотел?

Я читал много, что говорит, что я не должен называть GC.Collect(), но если GC не собирается в этой ситуации, что еще я могу сделать?

    private ObservableCollection<byte[]> memoryChunks = new ObservableCollection<byte[]>();
    public ObservableCollection<byte[]> MemoryChunks
    {
        get { return this.memoryChunks; }
    }

    private void AddButton_Click(object sender, RoutedEventArgs e)
    {
        // Create a 1 gig chunk of memory and add it to the collection.
        // It should not be garbage collected as long as it in the collection.

        try
        {
            byte[] chunk = new byte[1024*1024*1024];

            // Looks like I need to populate memory otherwise it doesn't show up in task manager
            for (int i = 0; i < chunk.Length; i++)
            {
                chunk[i] = 100;
            }

            this.memoryChunks.Add(chunk);                
        }
        catch (Exception ex)
        {
            MessageBox.Show(string.Format("Could not create another chunk: {0}{1}", Environment.NewLine, ex.ToString()));
        }
    }

    private void RemoveButton_Click(object sender, RoutedEventArgs e)
    {
        // By removing the chunk from the collection, 
        // I except no object has a reference to it, 
        // so it should be garbage collectable.

        if (memoryChunks.Count > 0)
        {
            memoryChunks.RemoveAt(0);
        }
    }

    private void GCButton_Click(object sender, RoutedEventArgs e)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
4b9b3361

Ответ 1

Как проверка работоспособности, у меня есть кнопка для принудительного включения GC. Когда я нажимаю это, я быстро возвращаю 6 ГБ. Разве это не доказывает, что мои 6 массивов не были указаны и МОГУТ быть собраны, если бы GC знал/хотел?

Вероятно, вам лучше спросить When does the GC automatically collect "garbage" memory?. Сверху моей головы:

  • Чаще всего, когда генерация 0 заполнена или распределение объектов не помещается в доступное свободное пространство. 1
  • Как правило, при распределении блока памяти возникает OutOfMemoryException, запускается полный GC, чтобы сначала попробовать и вернуть доступную память. Если после коллекции не будет достаточной непрерывной памяти, тогда будет выбрано исключение OOM.

При запуске сбора мусора GC определяет, какие поколения нужно собирать (0, 0 + 1 или все). Каждое поколение имеет размер, определяемый GC (он может меняться по мере запуска приложения). Если только поколение 0 превысит его бюджет, то это единственное поколение, чей мусор будет собран. Если объекты, оставшиеся в поколении 0, заставят генерацию 1 переходить на ее бюджет, то также будет собрано поколение 1, и его выживающие объекты будут продвинуты до поколения 2 (что является самым высоким поколением в реализации Microsoft). Если также будет превышен бюджет для поколения 2, мусор будет собран, но объекты не могут быть переведены в более высокое поколение, поскольку их не существует.

Итак, вот важная информация, наиболее распространенный способ запуска GC, поколение 2 будет собрано только в том случае, если поколение 0 и поколение 1 заполнены. Кроме того, вам нужно знать, что объекты размером более 85 000 байт не хранятся в обычной куче GC с генерацией 0, 1 и 2. Они фактически хранятся в так называемой большой куче объекта (LOT). Память в LOH освобождается только во время сбора FULL (т.е. При сборке 2); никогда, когда собираются только поколения 0 или 1.

Почему GC не собирался? Если это не время для этого, когда?

Теперь должно быть очевидно, почему GC никогда не происходило автоматически. Вы только создаете объекты в LOH (помните, что типы int, как вы их использовали, выделяются в стеке и их не нужно собирать). Вы никогда не заполняете поколение 0, поэтому GC никогда не бывает. 1

Вы также запускаете его в 64-битном режиме, а это означает, что вы вряд ли попадете в другой случай, который я перечислял выше, где происходит сбор, когда во всем приложении недостаточно памяти для размещения определенного объекта. Для 64-битных приложений ограничение виртуального адресного пространства составляет 8 ТБ, поэтому через некоторое время вы столкнетесь с этим случаем. Скорее всего, у вас закончится физическая память и файловое пространство страницы, прежде чем это произойдет.

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

Я читал много, что говорит, что я не должен называть GC.Collect(), но если GC не собирается в этой ситуации, что еще я могу сделать?

Вызовите GC.Collect(), если этот тип кода - это то, что вам нужно написать. Еще лучше, не пишите этот код вне тестирования.

В заключение я не сделал справедливости в отношении вопроса об автоматической сборке мусора в CLR. Я рекомендую прочитать об этом (это действительно очень интересно) через сообщения в блоге msdn или, как уже упоминалось, отличную книгу Джеффри Рихтера, CLR Via С#, глава 21.


1 Я исхожу из того, что вы понимаете, что реализация .NET в GC представляет собой сборщик мусора для поколений. В простейших терминах это означает, что вновь созданные объекты находятся в поколении с более низким номером, т.е. Поколении 0. По мере того как сборки мусора запускаются и обнаруживается, что объект, который находится в определенном поколении, имеет корень GC (а не "мусор" ) он будет повышен до следующего поколения. Это улучшение производительности, поскольку GC может занять много времени и повредить производительность. Идея состоит в том, что объекты в более высоких поколениях, как правило, имеют более длительный срок службы и будут использоваться в приложении дольше, поэтому нет необходимости проверять это поколение для мусора так же, как и более низкие поколения. Вы можете прочитать больше в этой статье в википедии. Вы заметите, что это также называется эфемерным GC.

2Если вы не верите мне, после удаления одного из кусков, есть функция, которая создает целую кучу случайных строк или объектов (я бы рекомендовал против массивов примитивов для этого теста), и вы увидите, когда вы достигнете определенное количество пространства, будет иметь место полный GC, освободив память, которую вы выделили в LOH.

Ответ 2

Это происходит в LOH (Big Object Heap). Они очищаются только при выполнении коллекции Generation 2. Как сказал Ханс в своем комментарии, вам понадобится больше ОЗУ, если это настоящий код.

Для хихиканья вы можете вызвать GC.GetGeneration(chunk), чтобы увидеть, что он вернет 2.

См. CLR через С#, 3-е издание Джеффри Рихтера (стр. 588).

Ответ 3

Для реального кода, который должен выделять огромный объем данных, а не выпускать, следует рассмотреть возможность вызова GC (GC.Collect), когда вы закончите с огромными объект.

Вы также можете переместить объекты из LOH в обычную кучу, сделав выделение в меньших (менее 80K) кусках.