Почему объект может пережить сборку мусора и перейти в старшее поколение в C#
Что проверяют
Знаете ли вы поколенческую модель сборщика и, главное, конкретные причины, по которым объект переживает сборку. Общие слова про «на него есть ссылки» не засчитываются, ждут корней и особых случаев вроде финализаторов и больших объектов.
Как отвечать
Объект живёт, пока достижим от корня: локальных переменных живых кадров, статических полей, дескрипторов GCHandle, очереди финализации. Недостижимость это условие сборки, а не обещание немедленно освободить память.
Поколения нужны из-за гипотезы, что большинство объектов умирают молодыми. Нулевое поколение собирается часто и дёшево, пережившее сборку уезжает в первое, пережившее её во второе. Продвижение это не награда, а признание: объект доказал живучесть, и часто заглядывать к нему смысла нет.
Дальше назовите причины выживания, ради которых и задан вопрос. Ссылка из статического поля или из кэша, который никто не ограничивает. Подписка на событие: издатель держит подписчика, и тот живёт ровно столько же. Финализатор, из-за которого объект переживает минимум одну сборку и утаскивает за собой всё, на что ссылается. Размер больше примерно 85 килобайт: такой объект сразу попадает в кучу больших объектов и собирается вместе со вторым поколением. Ссылка из старого объекта на молодой, о которой сборщик узнаёт через таблицу карт.
Практический вывод: растущее второе поколение это симптом утечки, а лечится она снятием подписок и ограничением кэшей.
Где спотыкаются
Считают, что Dispose освобождает память. Он освобождает неуправляемые ресурсы, файлы и соединения, и подавляет финализацию. Память вернёт только сборщик и только когда объект станет недостижимым, поэтому шаблон using с утечкой памяти прекрасно уживается.
Зовут GC.Collect для проверки или ускорения. Принудительная полная сборка сбивает подстройку сборщика под нагрузку и продвигает выжившее в старшее поколение раньше времени, то есть делает ровно обратное.
Объясняют всё одними ссылками и не вспоминают про большие объекты и события. Именно массив на сотню тысяч элементов, который никогда не был молодым, и неотписанный обработчик дают типовую картину «память растёт, а по коду всё освобождается».