Понимание коллекции мусора в .NET.


304 ов принято

Вы тут спотыкались и делаете очень неправильные выводы, потому что используете отладчик. Вам нужно будет запустить свой код так, как он работает на компьютере пользователя. Перейдите в сборку Release сначала с помощью менеджера сборки + Configuration, измените комбинацию «Active solution configuration» в левом верхнем углу, чтобы «Отпустить». Затем перейдите в Инструменты + Параметры, Отладка, Общие и отключите опцию «Подавить оптимизацию JIT».

Теперь запустите свою программу снова и поработайте с исходным кодом. Обратите внимание, что дополнительные фигурные скобки не имеют никакого эффекта. И обратите внимание, что установка переменной в значение null не имеет никакого значения. Он всегда будет печатать «1». Теперь он работает так, как вы надеетесь, и ожидал, что это сработает.

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

Во-первых, джиттер выполняет две важные обязанности, когда компилирует IL для метода в машинный код. Первый из них очень заметен в отладчике, вы можете увидеть машинный код с окном Debug + Windows + Disassembly. Вторая обязанность, однако, полностью невидима. Он также генерирует таблицу, которая описывает, как используются локальные переменные внутри тела метода. В этой таблице есть запись для каждого аргумента метода и локальной переменной с двумя адресами. Адрес, в котором переменная сначала сохранит ссылку на объект. И адрес инструкции машинного кода, где эта переменная больше не используется. Также сохраняется ли эта переменная в кадре стека или в регистре процессора.

Эта таблица важна для сборщика мусора, она должна знать, где искать ссылки на объекты, когда она выполняет сбор. Довольно легко сделать, когда ссылка является частью объекта в куче GC. Определенно нелегко сделать, когда ссылка на объект хранится в регистре CPU. В таблице говорится, где искать.

«Больше не использованный» адрес в таблице очень важен. Это делает сборщик мусора очень эффективным . Он может собирать ссылку на объект, даже если он используется внутри метода и этот метод еще не завершил выполнение. Что очень часто, ваш метод Main (), например, будет прекращать выполнение только до завершения вашей программы. Очевидно, что вам не нужны ссылки на объекты, используемые внутри этого метода Main (), чтобы жить в течение всего времени программы, что будет означать утечку. Джиттер может использовать таблицу, чтобы обнаружить, что такая локальная переменная больше не полезна, в зависимости от того, насколько далеко продвинулась программа внутри этого метода Main () до совершения вызова.

Почти магический метод, связанный с этой таблицей, - GC.KeepAlive (). Это очень специальный метод, он вообще не генерирует никакого кода. Его единственная обязанность - изменить эту таблицу. Он продлевает время жизни локальной переменной, предотвращая хранение ссылки, полученной от сбора мусора. Единственный раз, когда вам нужно использовать его, - это остановить GC от чрезмерного стремления к сбору ссылки, которая может произойти в сценариях взаимодействия, где ссылка передается на неуправляемый код. Сборщик мусора не может видеть, что такие ссылки используются таким кодом, поскольку он не был скомпилирован джиттером, поэтому нет таблицы, в которой говорится, где искать ссылку. Передача объекта делегата неуправляемой функции, такой как EnumWindows (), является примером шаблона, когда вам нужно использовать GC.KeepAlive ().

Итак, как вы можете сказать из вашего образца фрагмента после запуска его в сборке Release, локальные переменные могут быть собраны раньше, прежде чем закончить выполнение метода. Еще более мощно, объект может быть собран, пока один из его методов работает, если этот метод больше не ссылается на это . Существует проблема с этим, очень неудобно отлаживать такой метод. Так как вы можете поместить переменную в окно «Смотреть» или проверить ее. И он исчезнет, когда вы будете отлаживаться, если произойдет GC. Это было бы очень неприятно, поэтому дрожание знает, что есть отладчик. Затем он изменяет таблицу и изменяет «последний использованный» адрес. И изменяет его от нормального значения на адрес последней инструкции в методе. Который сохраняет переменную до тех пор, пока метод не вернулся. Это позволяет вам следить за ним до тех пор, пока метод не вернется.

Это теперь также объясняет, что вы видели раньше, и почему вы задали этот вопрос. Он печатает «0», потому что вызов GC.Collect не может собрать ссылку. В таблице указано, что переменная используется после вызова GC.Collect (), вплоть до конца метода. Вынужден сказать об этом, подключив отладчик и запустив сборку Debug.

Установка переменной в значение null имеет эффект теперь, потому что GC проверит переменную и больше не увидит ссылку. Но убедитесь, что вы не попадаете в ловушку, в которую попали многие программисты на C #, на самом деле написав этот код, было бессмысленно. Не имеет значения, присутствует ли этот оператор при запуске кода в сборке Release. Фактически, оптимизатор джиттера удалит это утверждение, поскольку он не имеет никакого эффекта. Поэтому не забудьте написать такой код, хотя это, казалось, имело эффект.


Одна заключительная заметка об этой теме - вот что заставляет программистов в беде писать небольшие программы, чтобы что-то делать с приложением Office. Отладчик обычно получает их на Неправильный путь, они хотят, чтобы программа Office выходила по требованию. Соответствующий способ сделать это - вызвать GC.Collect (). Но они обнаружат, что это не сработает, когда они отлаживают свое приложение, приводя их в никогда не землю, вызывая Marshal.ReleaseComObject (). Ручное управление памятью, оно редко работает должным образом, потому что они легко упускают из виду невидимую ссылку на интерфейс. GC.Collect () действительно работает, просто не при отладке приложения.


25

[Просто хотел добавить еще дальше процесс Internals of Finalization]

Таким образом, вы создаете объект, и когда объект собирается, Finalizeдолжен быть вызван метод объекта . Но есть еще до финализации, чем это очень простое предположение.

КОРОТКИЕ КОНЦЕПЦИИ ::

  1. Объекты НЕ реализуют Finalizeметоды, там Память исправляется немедленно, если, конечно, они не могут быть повторно загружены с помощью
    кода приложения

  2. Объекты , реализующие Application Rootsметод, концепция / Внедрение Finalization Queue, Freacheable Queue, Finalizeприходит , прежде чем они могут быть возвращены.

  3. Любой объект считается мусором, если он не доступен для кода приложения

Предположим :: Классы / Объекты А, В, D, G, Н не реализуют Finalizeспособ и C, E, F, I, J реализовать Finalizeметод.

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

поэтому указатели на объекты C, E, F, I, J добавляются в очередь завершения. Очереди финализации является внутренней структурой данных под контролем сборщика мусора. Каждая запись в очереди указывает на объект, который должен вызвать свой метод до того, как память объекта будет восстановлена. На рисунке ниже показана куча, содержащая несколько объектов. Некоторые из этих объектов доступны из корней приложения , а некоторые - нет. Когда были созданы объекты C, E, F, I и J, инфраструктура .Net обнаруживает, что эти объекты имеют методы и указатели на эти объекты, добавляются в очередь финализации .

FinalizeFinalize

введите описание изображения здесь

Когда происходит GC (первая коллекция), объекты B, E, G, H, I и J определяются как мусор. Поскольку A, C, D, F по-прежнему доступны в виде кода приложения, изображенного стрелками из желтой коробки выше.

Сборщик мусора просматривает очередь финализации, ища указатели на эти объекты. Когда указатель найден, указатель удаляется из очереди финализации и добавляется в свободную очередь («F-достижимая»). Freachable очереди еще одна внутренняя структура данных под контролем сборщика мусора. Каждый указатель в freachable queue идентифицирует объект, который готов вызвать его метод.

Finalize

После коллекции (1-я коллекция) управляемая куча выглядит примерно так, как показано на рисунке ниже. Объяснение, приведенное ниже:
1.) Память, занятая объектами B, G и H, была немедленно восстановлена, потому что у этих объектов не был метод Finalize, который нужно было вызывать .

2.) Однако память, занятая объектами E, I и J, не может быть восстановлена, потому что их Finalizeметод еще не был вызван. Вызов метода Finalize выполняется с помощью freacheable queue.

3.) A, C, D, F по-прежнему доступны в виде кода приложения, изображенного стрелками из желтой коробки выше, поэтому они НЕ будут собраны в любом случае

введите описание изображения здесь

Существует специальный поток времени выполнения, посвященный вызовам методов Finalize. Когда freachable queue пуст (как правило, это так), этот поток засыпает. Но когда появляются записи, этот поток просыпается, удаляет каждую запись из очереди и вызывает метод Finalize каждого объекта. Сборщик мусора сжимает возвращаемую память, и специальный поток времени запуска освобождает свободную очередь, выполняя Finalizeметод каждого объекта . Итак, вот, наконец, когда ваш метод Finalize выполняется

В следующий раз, когда вызывается сборщик мусора (2-я сборка), он видит, что финализированные объекты действительно мусор, так как корни приложения не указывают на него, а лишняя очередь больше не указывает на него (это тоже ПУСТОЙ), поэтому память для объектов (E, I, J) просто восстанавливается из диаграммы Heap.See ниже и сравнивает ее с рисунком чуть выше

введите описание изображения здесь

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

ПРИМЕЧАНИЕ . Флашируемая очередь считается корнем, так же как глобальные и статические переменные являются корнями. Поэтому, если объект находится в freachable queue, тогда объект доступен и не является мусором.

В качестве последней заметки помните, что приложение для отладки - это одно, а Garbage Collection - это другое дело и работает по-другому. До сих пор вы не можете ЧУВСТВИТЬ сборку мусора, просто отлаживая приложения, а дальше, если вы хотите исследовать память, начните здесь.


1

Существует 3 способа управления памятью:

GC работает только для управляемых ресурсов, поэтому .NET предоставляет Dispose и Finalize для выпуска неуправляемых ресурсов, таких как поток, подключение к базе данных, объекты COM и т. Д.

1) Утилизировать

Dispose должен быть вызван явно для типов, которые реализуют IDisposable.

Программист должен вызвать это либо с помощью Dispose (), либо с помощью функции «Построение»

Используйте GC.SuppressFinalize (это), чтобы предотвратить вызов Finalizer, если вы уже использовали dispose ()

2) Завершить или отделить

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

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

3) GC.Collect ()

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

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

Используйте WaitForPendingFinalizers, если вы хотите убедиться, что все финализаторы были вызваны после вызова GC.Collect ()

Обратитесь к сообщению в моем блоге, где у меня есть эта статья: - Сбор мусора в .NET.

C # ,. чистый, мусор сбора,

c#,.net,garbage-collection,

137

Ответов: 3


304 ов принято

Вы тут спотыкались и делаете очень неправильные выводы, потому что используете отладчик. Вам нужно будет запустить свой код так, как он работает на компьютере пользователя. Перейдите в сборку Release сначала с помощью менеджера сборки + Configuration, измените комбинацию «Active solution configuration» в левом верхнем углу, чтобы «Отпустить». Затем перейдите в Инструменты + Параметры, Отладка, Общие и отключите опцию «Подавить оптимизацию JIT».

Теперь запустите свою программу снова и поработайте с исходным кодом. Обратите внимание, что дополнительные фигурные скобки не имеют никакого эффекта. И обратите внимание, что установка переменной в значение null не имеет никакого значения. Он всегда будет печатать «1». Теперь он работает так, как вы надеетесь, и ожидал, что это сработает.

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

Во-первых, джиттер выполняет две важные обязанности, когда компилирует IL для метода в машинный код. Первый из них очень заметен в отладчике, вы можете увидеть машинный код с окном Debug + Windows + Disassembly. Вторая обязанность, однако, полностью невидима. Он также генерирует таблицу, которая описывает, как используются локальные переменные внутри тела метода. В этой таблице есть запись для каждого аргумента метода и локальной переменной с двумя адресами. Адрес, в котором переменная сначала сохранит ссылку на объект. И адрес инструкции машинного кода, где эта переменная больше не используется. Также сохраняется ли эта переменная в кадре стека или в регистре процессора.

Эта таблица важна для сборщика мусора, она должна знать, где искать ссылки на объекты, когда она выполняет сбор. Довольно легко сделать, когда ссылка является частью объекта в куче GC. Определенно нелегко сделать, когда ссылка на объект хранится в регистре CPU. В таблице говорится, где искать.

«Больше не использованный» адрес в таблице очень важен. Это делает сборщик мусора очень эффективным . Он может собирать ссылку на объект, даже если он используется внутри метода и этот метод еще не завершил выполнение. Что очень часто, ваш метод Main (), например, будет прекращать выполнение только до завершения вашей программы. Очевидно, что вам не нужны ссылки на объекты, используемые внутри этого метода Main (), чтобы жить в течение всего времени программы, что будет означать утечку. Джиттер может использовать таблицу, чтобы обнаружить, что такая локальная переменная больше не полезна, в зависимости от того, насколько далеко продвинулась программа внутри этого метода Main () до совершения вызова.

Почти магический метод, связанный с этой таблицей, - GC.KeepAlive (). Это очень специальный метод, он вообще не генерирует никакого кода. Его единственная обязанность - изменить эту таблицу. Он продлевает время жизни локальной переменной, предотвращая хранение ссылки, полученной от сбора мусора. Единственный раз, когда вам нужно использовать его, - это остановить GC от чрезмерного стремления к сбору ссылки, которая может произойти в сценариях взаимодействия, где ссылка передается на неуправляемый код. Сборщик мусора не может видеть, что такие ссылки используются таким кодом, поскольку он не был скомпилирован джиттером, поэтому нет таблицы, в которой говорится, где искать ссылку. Передача объекта делегата неуправляемой функции, такой как EnumWindows (), является примером шаблона, когда вам нужно использовать GC.KeepAlive ().

Итак, как вы можете сказать из вашего образца фрагмента после запуска его в сборке Release, локальные переменные могут быть собраны раньше, прежде чем закончить выполнение метода. Еще более мощно, объект может быть собран, пока один из его методов работает, если этот метод больше не ссылается на это . Существует проблема с этим, очень неудобно отлаживать такой метод. Так как вы можете поместить переменную в окно «Смотреть» или проверить ее. И он исчезнет, когда вы будете отлаживаться, если произойдет GC. Это было бы очень неприятно, поэтому дрожание знает, что есть отладчик. Затем он изменяет таблицу и изменяет «последний использованный» адрес. И изменяет его от нормального значения на адрес последней инструкции в методе. Который сохраняет переменную до тех пор, пока метод не вернулся. Это позволяет вам следить за ним до тех пор, пока метод не вернется.

Это теперь также объясняет, что вы видели раньше, и почему вы задали этот вопрос. Он печатает «0», потому что вызов GC.Collect не может собрать ссылку. В таблице указано, что переменная используется после вызова GC.Collect (), вплоть до конца метода. Вынужден сказать об этом, подключив отладчик и запустив сборку Debug.

Установка переменной в значение null имеет эффект теперь, потому что GC проверит переменную и больше не увидит ссылку. Но убедитесь, что вы не попадаете в ловушку, в которую попали многие программисты на C #, на самом деле написав этот код, было бессмысленно. Не имеет значения, присутствует ли этот оператор при запуске кода в сборке Release. Фактически, оптимизатор джиттера удалит это утверждение, поскольку он не имеет никакого эффекта. Поэтому не забудьте написать такой код, хотя это, казалось, имело эффект.


Одна заключительная заметка об этой теме - вот что заставляет программистов в беде писать небольшие программы, чтобы что-то делать с приложением Office. Отладчик обычно получает их на Неправильный путь, они хотят, чтобы программа Office выходила по требованию. Соответствующий способ сделать это - вызвать GC.Collect (). Но они обнаружат, что это не сработает, когда они отлаживают свое приложение, приводя их в никогда не землю, вызывая Marshal.ReleaseComObject (). Ручное управление памятью, оно редко работает должным образом, потому что они легко упускают из виду невидимую ссылку на интерфейс. GC.Collect () действительно работает, просто не при отладке приложения.


25

[Просто хотел добавить еще дальше процесс Internals of Finalization]

Таким образом, вы создаете объект, и когда объект собирается, Finalizeдолжен быть вызван метод объекта . Но есть еще до финализации, чем это очень простое предположение.

КОРОТКИЕ КОНЦЕПЦИИ ::

  1. Объекты НЕ реализуют Finalizeметоды, там Память исправляется немедленно, если, конечно, они не могут быть повторно загружены с помощью
    кода приложения

  2. Объекты , реализующие Application Rootsметод, концепция / Внедрение Finalization Queue, Freacheable Queue, Finalizeприходит , прежде чем они могут быть возвращены.

  3. Любой объект считается мусором, если он не доступен для кода приложения

Предположим :: Классы / Объекты А, В, D, G, Н не реализуют Finalizeспособ и C, E, F, I, J реализовать Finalizeметод.

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

поэтому указатели на объекты C, E, F, I, J добавляются в очередь завершения. Очереди финализации является внутренней структурой данных под контролем сборщика мусора. Каждая запись в очереди указывает на объект, который должен вызвать свой метод до того, как память объекта будет восстановлена. На рисунке ниже показана куча, содержащая несколько объектов. Некоторые из этих объектов доступны из корней приложения , а некоторые - нет. Когда были созданы объекты C, E, F, I и J, инфраструктура .Net обнаруживает, что эти объекты имеют методы и указатели на эти объекты, добавляются в очередь финализации .

FinalizeFinalize

введите описание изображения здесь

Когда происходит GC (первая коллекция), объекты B, E, G, H, I и J определяются как мусор. Поскольку A, C, D, F по-прежнему доступны в виде кода приложения, изображенного стрелками из желтой коробки выше.

Сборщик мусора просматривает очередь финализации, ища указатели на эти объекты. Когда указатель найден, указатель удаляется из очереди финализации и добавляется в свободную очередь («F-достижимая»). Freachable очереди еще одна внутренняя структура данных под контролем сборщика мусора. Каждый указатель в freachable queue идентифицирует объект, который готов вызвать его метод.

Finalize

После коллекции (1-я коллекция) управляемая куча выглядит примерно так, как показано на рисунке ниже. Объяснение, приведенное ниже:
1.) Память, занятая объектами B, G и H, была немедленно восстановлена, потому что у этих объектов не был метод Finalize, который нужно было вызывать .

2.) Однако память, занятая объектами E, I и J, не может быть восстановлена, потому что их Finalizeметод еще не был вызван. Вызов метода Finalize выполняется с помощью freacheable queue.

3.) A, C, D, F по-прежнему доступны в виде кода приложения, изображенного стрелками из желтой коробки выше, поэтому они НЕ будут собраны в любом случае

введите описание изображения здесь

Существует специальный поток времени выполнения, посвященный вызовам методов Finalize. Когда freachable queue пуст (как правило, это так), этот поток засыпает. Но когда появляются записи, этот поток просыпается, удаляет каждую запись из очереди и вызывает метод Finalize каждого объекта. Сборщик мусора сжимает возвращаемую память, и специальный поток времени запуска освобождает свободную очередь, выполняя Finalizeметод каждого объекта . Итак, вот, наконец, когда ваш метод Finalize выполняется

В следующий раз, когда вызывается сборщик мусора (2-я сборка), он видит, что финализированные объекты действительно мусор, так как корни приложения не указывают на него, а лишняя очередь больше не указывает на него (это тоже ПУСТОЙ), поэтому память для объектов (E, I, J) просто восстанавливается из диаграммы Heap.See ниже и сравнивает ее с рисунком чуть выше

введите описание изображения здесь

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

ПРИМЕЧАНИЕ . Флашируемая очередь считается корнем, так же как глобальные и статические переменные являются корнями. Поэтому, если объект находится в freachable queue, тогда объект доступен и не является мусором.

В качестве последней заметки помните, что приложение для отладки - это одно, а Garbage Collection - это другое дело и работает по-другому. До сих пор вы не можете ЧУВСТВИТЬ сборку мусора, просто отлаживая приложения, а дальше, если вы хотите исследовать память, начните здесь.


1

Существует 3 способа управления памятью:

GC работает только для управляемых ресурсов, поэтому .NET предоставляет Dispose и Finalize для выпуска неуправляемых ресурсов, таких как поток, подключение к базе данных, объекты COM и т. Д.

1) Утилизировать

Dispose должен быть вызван явно для типов, которые реализуют IDisposable.

Программист должен вызвать это либо с помощью Dispose (), либо с помощью функции «Построение»

Используйте GC.SuppressFinalize (это), чтобы предотвратить вызов Finalizer, если вы уже использовали dispose ()

2) Завершить или отделить

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

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

3) GC.Collect ()

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

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

Используйте WaitForPendingFinalizers, если вы хотите убедиться, что все финализаторы были вызваны после вызова GC.Collect ()

Обратитесь к сообщению в моем блоге, где у меня есть эта статья: - Сбор мусора в .NET.

C # ,. чистый, мусор сбора,
Похожие вопросы