24

Вопрос по c#, .net – Сборка мусора не происходит даже при необходимости

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

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

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

Когда я удаляю, я не вижу, как моя память падает. Но со мной все в порядке, потому что я понимаю, что 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's 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();
    }

Вы также можете начать с этой ссылки:<a href="http://msdn.microsoft.com/en-us/magazine/cc534993.aspx" rel="nofollow noreferrer">msdn.microsoft.com/en-us/magazine/cc534993.aspx</a>

Apr 04, 2012, 6:00 PMот

Поведение кучи больших объектов хорошо описано в любой справочной книге по CLR. Посетите свою библиотеку и зацените что-нибудь от Рихтера. Если вы выделяете много массивов в один гигабайт<i>real</i> код, тогда вам потребуется больше оперативной памяти. Любая машина может быть завалена искусственным кодом.

Apr 04, 2012, 5:54 PMот

3ответа

2

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

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

17

As a sanity check, I have a button to force GC. When I push that, I quickly get 6GB back. Doesn't that prove my 6 arrays were not being referenced and COULD have been collected had the GC knew/wanted to?

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

  • Most commonly, when generation 0 is full or an object allocation won't fit in the available free space.1
  • Somewhat commonly, when allocating a chunk of memory would cause an OutOfMemoryException, a full GC is triggered to first try and reclaim available memory. If not enough contiguous memory is available after the collection, then an OOM exception will be thrown.

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

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

Why didn't the GC collect? If that wasn't the time to do it, when is?

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

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

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

I've read a lot that says I shouldn't call GC.Collect() but if GC doesn't collect in this situation, what else can I do?

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

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


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

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

5

Это происходит на LOH (куча больших объектов). Они очищаются только при выполнении сбора поколения 2. Как только что сказал Ганс в своем комментарии, вам понадобится больше оперативной памяти, если это реальный код.

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

Пожалуйста, смотрите CLR via C #, 3-е издание Джеффри Рихтера (стр. 588).

RelatedQuestions