Вы действительно не можете делать общие заявления о подходящем способе использования всех реализаций GC. Они сильно различаются. Поэтому я поговорю с .NET, на который вы изначально ссылались.
Вы должны знать поведение GC довольно близко, чтобы сделать это с любой логикой или причиной.
Единственный совет по сбору, который я могу дать: никогда не делай этого.
Если вы действительно знаете сложные детали GC, вам не понадобится мой совет, так что это не будет иметь значения. Если вы еще не уверены со 100% уверенностью, что это поможет, вам придется поискать в Интернете и найти ответ, подобный следующему: вам не следует звонить в GC.Collect или, наоборот: вам следует узнать подробности работы GC. внутри и снаружи, и только тогда вы узнаете ответ .
Есть одно безопасное место, в котором есть смысл использовать GC.Collect :
GC.Collect - это доступный API, который вы можете использовать для профилирования времени. Вы могли бы профилировать один алгоритм, собирать и профилировать другой алгоритм сразу после этого, зная, что GC первого алгоритма не возникало во время вашего второго искажения результатов.
Такого рода профилирование - единственный раз, когда я советую собирать вручную кому-либо.
Во всяком случае, надуманный пример
Один из возможных вариантов использования - если вы загружаете действительно большие вещи, они окажутся в куче больших объектов, которая перейдет прямо к поколению 2, хотя опять же поколение 2 предназначено для долгоживущих объектов, потому что оно собирает реже Если вы знаете, что по какой-либо причине вы загружаете недолговечные объекты в Gen 2, вы можете очистить их быстрее, чтобы уменьшить размер Gen 2 и ускорить его сбор.
Это лучший пример, который я могу придумать, и он не очень хорош - давление LOH, которое вы создаете здесь, будет вызывать более частые коллекции, и коллекции настолько часты, насколько это возможно, - скорее всего, это будет очищать LOH так же, как так быстро, как вы взрывали его временными предметами. Я просто не доверяю себе, чтобы предполагать лучшую частоту сбора, чем сам GC - настроенный людьми, намного умнее меня.
Итак, давайте поговорим о некоторых семантиках и механизмах в .NET GC ... или ...
Все, что я думаю, я знаю о .NET GC
Пожалуйста, все, кто найдет здесь ошибки - исправьте меня. Хорошо известно, что большая часть GC - черная магия, и хотя я пытался опустить детали, в которых я не был уверен, я, вероятно, все еще ошибался.
Ниже преднамеренно упущены многочисленные детали, в которых я не уверен, а также гораздо больший объем информации, о которой я просто не знаю. Используйте эту информацию на свой страх и риск.
GC Concepts
.NET GC возникает в противоречивые моменты, поэтому он называется «недетерминированным», это означает, что вы не можете рассчитывать на то, что он произойдет в определенное время. Это также сборщик мусора поколений, что означает, что он разбивает ваши объекты на количество пройденных GC-проходов.
Объекты в куче Gen 0 прошли через 0 коллекций, они были созданы недавно, так как с момента их создания коллекция не возникла. Объекты в вашей куче Gen 1 пережили один проход сбора, и объекты в вашей куче Gen 2 пережили 2 прохода сбора.
Теперь стоит отметить причину, по которой он квалифицирует эти конкретные поколения и разделы соответственно. .NET GC распознает только эти три поколения, потому что проходы коллекции, проходящие через эти три кучи, немного отличаются. Некоторые объекты могут пережить коллекцию, пройденную тысячи раз. GC просто оставляет их на другой стороне раздела кучи Gen 2, нет никакого смысла разбивать их дальше, потому что они на самом деле Gen 44; передача коллекции на них такая же, как и в куче Gen 2.
У этих конкретных поколений есть семантические цели, а также реализованные механизмы, которые их соблюдают, и я расскажу об этом чуть позже.
Что в коллекции
Основная концепция этапа сбора GC состоит в том, что он проверяет каждый объект в пространстве кучи, чтобы увидеть, есть ли еще живые ссылки (корни GC) на эти объекты. Если для объекта найден корень GC, это означает, что выполняемый в настоящее время код все еще может достичь и использовать этот объект, поэтому его нельзя удалить. Однако, если корень GC не найден для объекта, это означает, что запущенный процесс больше не нуждается в объекте, поэтому он может удалить его, чтобы освободить память для новых объектов.
Теперь, после того как он завершит очистку группы объектов и оставит некоторые в покое, возникнет неприятный побочный эффект: свободные промежутки между живыми объектами, где были удалены мертвые. Эта фрагментация памяти, если оставить ее в покое, просто приведет к потере памяти, поэтому коллекции обычно делают так называемое «сжатие», когда они берут все оставшиеся живые объекты и сжимают их вместе в куче, чтобы свободная память была непрерывной на одной стороне кучи для Gen 0.
Теперь, учитывая идею трех кучи памяти, все разделены по количеству проходов сбора, которые они пережили, давайте поговорим о том, почему существуют эти разделы.
Коллекция Gen 0
Gen 0, будучи абсолютно новыми объектами, имеет тенденцию быть очень маленьким, так что вы можете безопасно собирать его очень часто . Частота гарантирует, что куча остается маленькой, а коллекции очень быстрыми, потому что они накапливаются в такой маленькой куче. Это основано более или менее на эвристике, которая утверждает: подавляющее большинство создаваемых вами временных объектов являются очень временными, поэтому временными они больше не будут использоваться или на них не будут ссылаться почти сразу после использования, и, следовательно, их можно собирать.
Коллекция Gen 1
Бытие 1, являющееся объектами, которые не попадают в эту очень временную категорию объектов, все еще может быть довольно недолгим, потому что большая часть созданных объектов не используется долго. Поэтому Gen 1 также собирает довольно часто, опять же, сохраняя кучу небольшого размера, поэтому сборы происходят быстро. Однако предположение менее из его объектов являются временными , чем Gen 0, поэтому он собирает реже , чем Gen 0
Я скажу, что, честно говоря, я не знаю технических механизмов, которые отличаются между проходом сбора Gen 0 и Gen 1, если они вообще существуют, кроме частоты, которую они собирают.
Gen 2 Collection
Gen 2 теперь должен быть матерью всех куч, не так ли? Ну да, это более или менее правильно. Это место, где живут все ваши постоянные объекты - Main()
например, объект, в котором вы живете, и все, на что Main()
ссылаются, потому что они будут внедрены до тех пор, пока вы не Main()
вернетесь в конце вашего процесса.
Учитывая, что Gen 2 - это ведро для всего, что не могли собрать другие поколения, его объекты в значительной степени постоянны или, по крайней мере, долго живут. Таким образом, признание очень небольшого количества того, что есть в Gen 2, на самом деле будет чем-то, что можно собирать, и не нужно собирать его часто. Это позволяет собирать данные медленнее, так как они выполняются гораздо реже. Так что именно здесь они взялись за все дополнительные варианты поведения для нечетных сценариев, потому что у них есть время на их выполнение.
Куча больших объектов
Одним из примеров дополнительного поведения Gen 2 является то, что он также выполняет сбор данных в куче больших объектов. До сих пор я полностью говорил о куче малых объектов, но среда выполнения .NET выделяет вещи определенного размера в отдельную кучу из-за того, что я назвал выше уплотнением. Сжатие требует перемещения объектов, когда коллекции заканчиваются в куче мелких объектов. Если в Gen 1 есть живой объект размером 10 МБ, ему понадобится гораздо больше времени, чтобы завершить уплотнение после сбора, что замедлит сбор данных в Gen 1. Таким образом, объект размером 10 Мб выделяется для кучи больших объектов и собирается во время второго поколения, которое выполняется так редко.
завершение
Другой пример - объекты с финализаторами. Вы помещаете финализатор на объект, который ссылается на ресурсы, выходящие за рамки .NET GC (неуправляемые ресурсы). Финализатор - это единственный способ, которым сборщик мусора может потребовать сбора неуправляемых ресурсов - вы реализуете свой финализатор для ручного сбора / удаления / освобождения неуправляемого ресурса, чтобы гарантировать, что он не просочится из вашего процесса. Когда GC приступит к выполнению финализатора ваших объектов, ваша реализация очистит неуправляемый ресурс, что сделает GC способным удалить ваш объект без риска утечки ресурса.
Механизм, с помощью которого финализаторы делают это, напрямую связан с очередью финализации. Когда среда выполнения выделяет объект с помощью финализатора, он добавляет указатель на этот объект в очередь финализации и блокирует ваш объект на месте (так называемое закрепление), так что сжатие не будет перемещать его, что нарушит ссылку на очередь финализации. По мере прохождения сбора, в конечном итоге будет обнаружено, что ваш объект больше не имеет корня GC, но для его сбора необходимо выполнить финализацию. Поэтому, когда объект мертв, коллекция будет перемещать свою ссылку из очереди завершения и помещать ссылку на него в так называемую очередь «FReachable». Затем коллекция продолжается. В другое «недетерминированное» время в будущем, отдельный поток, известный как поток финализатора, пройдет через очередь FReachable, выполняя финализаторы для каждого из объектов, на которые имеются ссылки. После завершения очередь FReachable становится пустой, и она слегка переворачивает заголовок каждого объекта, который говорит, что им не требуется финализация (этот бит также можно перевернуть вручную с помощьюGC.SuppressFinalize
что часто встречается в Dispose()
методах), я также подозреваю, что это открепило объекты, но не цитируйте меня по этому поводу. Следующая коллекция, находящаяся в куче этого объекта, наконец соберет его. Коллекции Gen 0 даже не обращают внимания на объекты с этим битом, необходимым для финализации, он автоматически продвигает их, даже не проверяя их корень. Некорневый объект, нуждающийся в финализации в Gen 1, будет брошен в FReachable
очередь, но коллекция ничего с ним не делает, поэтому он живет в Gen 2. Таким образом, все объекты, которые имеют финализатор, и не GC.SuppressFinalize
будут собраны в Gen 2.