В компьютерном программировании сборка мусора с трассировкой — это форма автоматического управления памятью , которая заключается в определении того, какие объекты должны быть освобождены («сборка мусора»), путем отслеживания того, какие объекты доступны по цепочке ссылок из определенных «корневых» объектов, и рассмотрения остальных как «мусора» и их сбора. Трассировка — наиболее распространенный тип сборки мусора — настолько, что «сборка мусора» часто относится к методу трассировки, а не к другим, таким как подсчет ссылок , — и существует большое количество алгоритмов, используемых при реализации.
Неформально, объект достижим, если на него ссылается хотя бы одна переменная в программе, либо напрямую, либо через ссылки из других достижимых объектов. Точнее, объекты могут быть достижимы только двумя способами:
Определение достижимости "мусора" не является оптимальным, поскольку последний раз, когда программа использует объект, может быть задолго до того, как этот объект выйдет из области видимости окружения. Иногда проводится различие между синтаксическим мусором , те объекты, которых программа, возможно, не сможет достичь, и семантическим мусором , те объекты, которые программа фактически никогда больше не будет использовать. Например:
Object x = new Foo (); Object y = new Bar (); x = new Quux (); /* На этом этапе мы знаем, что объект Foo , * изначально назначенный x, никогда не будет * доступен: это синтаксический мусор. */ /* В следующем блоке y *может* быть семантическим мусором; * но мы не узнаем этого, пока x.check_something() не вернет * какое-то значение -- если оно вообще вернет. */ if ( x . check_something ()) { x . do_something ( y ); } System . exit ( 0 );
Проблема точного определения семантического мусора может быть легко показана как частично разрешимая : программа, которая выделяет объект , запускает произвольную входную программу и использует if и только if, завершается, потребует семантического сборщика мусора для решения проблемы остановки . Хотя консервативные эвристические методы обнаружения семантического мусора остаются активной областью исследований, по сути, все практические сборщики мусора сосредоточены на синтаксическом мусоре. [ требуется цитата ]
Еще одна сложность с этим подходом заключается в том, что в языках с типами ссылок и неупакованными типами значений сборщик мусора должен каким-то образом различать, какие переменные в стеке или поля в объекте являются обычными значениями, а какие — ссылками: в памяти целое число и ссылка могут выглядеть одинаково. Затем сборщику мусора нужно знать, следует ли рассматривать элемент как ссылку и следовать ей, или это примитивное значение. Одним из распространенных решений является использование помеченных указателей .
Сборщик мусора может удалить только те объекты, на которые нет ссылок, указывающих на них напрямую или косвенно из корневого набора. Однако некоторые программы требуют слабых ссылок , которые должны быть пригодны для использования до тех пор, пока существует объект, но не должны продлевать его срок службы. В обсуждениях слабых ссылок обычные ссылки иногда называют сильными ссылками . Объект подлежит сборке мусора, если на него нет сильных (т. е. обычных) ссылок, даже если на него все еще могут быть некоторые слабые ссылки.
Слабая ссылка — это не просто указатель на объект, который не интересует сборщика мусора. Этот термин обычно зарезервирован для должным образом управляемой категории специальных ссылочных объектов, которые можно безопасно использовать даже после исчезновения объекта, поскольку они переходят к безопасному значению (обычно null
). Небезопасная ссылка, которая не известна сборщику мусора, просто останется висячей, продолжая ссылаться на адрес, где ранее находился объект. Это не слабая ссылка.
В некоторых реализациях слабые ссылки делятся на подкатегории. Например, виртуальная машина Java предоставляет три формы слабых ссылок, а именно мягкие ссылки , [1] фантомные ссылки , [2] и обычные слабые ссылки. [3] Мягко ссылаемый объект может быть утилизирован только в том случае, если сборщик мусора решит, что программе не хватает памяти. В отличие от мягкой ссылки или обычной слабой ссылки, фантомная ссылка не предоставляет доступ к объекту, на который она ссылается. Вместо этого фантомная ссылка — это механизм, который позволяет сборщику мусора уведомлять программу, когда ссылаемый объект становится фантомно достижимым . Объект является фантомно достижимым, если он все еще находится в памяти и на него ссылается фантомная ссылка, но его финализатор уже выполнился. Аналогично, Microsoft.NET предоставляет две подкатегории слабых ссылок, [4] а именно длинные слабые ссылки (отслеживает воскрешение) и короткие слабые ссылки.
Также можно разработать структуры данных , которые имеют слабые функции отслеживания. Например, полезны слабые хэш-таблицы . Как и обычная хэш-таблица, слабая хэш-таблица поддерживает связь между парами объектов, где каждая пара понимается как ключ и значение. Однако хэш-таблица на самом деле не поддерживает сильную ссылку на эти объекты. Особое поведение имеет место, когда либо ключ, либо значение, либо и то, и другое становится мусором: запись хэш-таблицы спонтанно удаляется. Существуют и другие уточнения, такие как хэш-таблицы, которые имеют только слабые ключи (ссылки на значения являются обычными, сильные ссылки) или только слабые значения (ссылки на ключи являются сильными).
Слабые хеш-таблицы важны для поддержания ассоциаций между объектами, так как объекты, участвующие в ассоциации, могут стать мусором, если в программе больше нет ссылок на них (кроме ассоциированной хеш-таблицы).
Использование обычной хеш-таблицы для такой цели может привести к «логической утечке памяти»: накоплению доступных данных, которые программе не нужны и не будут использоваться.
Сборщики трассировки так называются, потому что они прослеживают рабочий набор памяти. Эти сборщики мусора выполняют сборку циклами. Циклы часто запускаются, когда у менеджера памяти недостаточно свободной памяти для удовлетворения запроса на выделение. Но циклы часто могут запрашиваться мутатором напрямую или запускаться по расписанию. Первоначальный метод включает в себя наивную пометку и очистку , при которой весь набор памяти затрагивается несколько раз.
В наивном методе пометки и очистки каждый объект в памяти имеет флаг (обычно один бит), зарезервированный только для использования при сборке мусора. Этот флаг всегда очищается , за исключением цикла сборки.
Первый этап — этап маркировки , который выполняет обход дерева всего «корневого набора» и помечает каждый объект, на который указывает корень, как «используемый». Все объекты, на которые указывают эти объекты и т. д., также помечаются, так что каждый объект, достижимый через корневой набор, помечается.
На втором этапе, этапе очистки , вся память сканируется от начала до конца, проверяются все свободные или используемые блоки; те, которые не помечены как «используемые», недоступны никаким корням, и их память освобождается. Для объектов, которые были помечены как используемые, флаг использования очищается, готовясь к следующему циклу.
Этот метод имеет несколько недостатков, наиболее заметным из которых является то, что вся система должна быть приостановлена во время сбора; никакая мутация рабочего набора не может быть разрешена. Это может привести к тому, что программы будут периодически «зависать» (и, как правило, непредсказуемо), делая некоторые приложения реального времени и критические по времени приложения невозможными. Кроме того, вся рабочая память должна быть проверена, большая ее часть дважды, что потенциально может вызвать проблемы в системах страничной памяти .
Из-за этих проблем с производительностью большинство современных сборщиков мусора с трассировкой реализуют некоторые варианты абстракции трехцветной маркировки , но простые сборщики (такие как сборщик пометки и очистки ) часто не делают эту абстракцию явной. Трехцветная маркировка работает так, как описано ниже.
Созданы три комплекта – белый , черный и серый :
Во многих алгоритмах изначально черный набор пуст, серый набор — это набор объектов, на которые напрямую ссылаются корни, а белый набор включает все остальные объекты. Каждый объект в памяти всегда находится ровно в одном из трех наборов. Алгоритм работает следующим образом:
Когда серый набор пуст, сканирование завершено; черные объекты доступны из корней, а белые — нет и могут быть удалены сборщиком мусора.
Поскольку все объекты, не достижимые непосредственно из корней, добавляются в белый набор, а объекты могут перемещаться только из белого в серый и из серого в черный, алгоритм сохраняет важный инвариант — ни один черный объект не ссылается на белый объект. Это гарантирует, что белые объекты могут быть освобождены, как только серый набор опустеет. Это называется трехцветным инвариантом . Некоторые вариации алгоритма не сохраняют этот инвариант, а используют измененную форму, для которой сохраняются все важные свойства.
Трехцветный метод имеет важное преимущество – его можно выполнять «на лету», не останавливая систему на значительные периоды времени. Это достигается путем маркировки объектов по мере их выделения и во время мутации, поддерживая различные наборы. Контролируя размер наборов, система может выполнять сборку мусора периодически, а не по мере необходимости. Кроме того, избегается необходимость касаться всего рабочего набора в каждом цикле.
После определения недостижимого множества сборщик мусора может просто освободить недостижимые объекты и оставить все остальное как есть, или может скопировать некоторые или все достижимые объекты в новую область памяти, обновив все ссылки на эти объекты по мере необходимости. Они называются «неподвижными» и «подвижными» (или, альтернативно, «неуплотняющими» и «уплотняющими») сборщиками мусора соответственно.
Сначала движущийся алгоритм может показаться неэффективным по сравнению с неподвижным, поскольку, по-видимому, требуется гораздо больше работы на каждом цикле. Но движущийся алгоритм приводит к нескольким преимуществам производительности, как во время самого цикла сборки мусора, так и во время выполнения программы:
Одним из недостатков перемещающегося сборщика мусора является то, что он разрешает доступ только через ссылки, которые управляются средой сбора мусора, и не допускает арифметику указателей . Это связано с тем, что любые указатели на объекты будут аннулированы, если сборщик мусора переместит эти объекты (они станут висячими указателями ). Для взаимодействия с собственным кодом сборщик мусора должен скопировать содержимое объекта в место за пределами области памяти, в которой собирается мусор. Альтернативный подход заключается в закреплении объекта в памяти, что не позволяет сборщику мусора перемещать его и позволяет напрямую совместно использовать память с собственными указателями (и, возможно, разрешает арифметику указателей). [5]
Коллекционеры различаются не только по тому, перемещаются они или нет, их также можно классифицировать по тому, как они обращаются с белыми, серыми и черными наборами предметов во время цикла сбора.
Самый простой подход — полупространственный сборщик , который датируется 1969 годом. В этом движущемся сборщике память разделена на равные по размеру «из пространства» и «в пространство». Первоначально объекты выделяются в «в пространство», пока оно не заполнится и не запустится цикл сбора. В начале цикла «в пространство» становится «из пространства», и наоборот. Объекты, доступные из корневого набора, копируются из «из пространства» в «в пространство». Эти объекты сканируются по очереди, и все объекты, на которые они указывают, копируются в «в пространство», пока все доступные объекты не будут скопированы в «в пространство». Как только программа продолжает выполнение, новые объекты снова выделяются в «в пространство», пока оно снова не заполнится, и процесс повторяется.
Этот подход очень прост, но поскольку для выделения объектов используется только одно полупространство, использование памяти в два раза выше по сравнению с другими алгоритмами. Этот метод также известен как stop-and-copy . Алгоритм Чейни является улучшением полупространственного сборщика.
Сборщик мусора «отметить и зачистить» сохраняет бит или два с каждым объектом, чтобы записать, белый он или черный. Серый набор хранится в виде отдельного списка или использует другой бит. По мере обхода дерева ссылок во время цикла сбора (фаза «отметить») сборщик манипулирует этими битами. Затем финальная «зачистка» областей памяти освобождает белые объекты. Стратегия «отметить и зачистить» имеет то преимущество, что после определения осужденного набора можно использовать либо перемещаемую, либо неподвижную стратегию сбора. Этот выбор стратегии может быть сделан во время выполнения, если позволяет доступная память. Недостатком является «раздувание» объектов на небольшое количество, то есть каждый объект имеет небольшую скрытую стоимость памяти из-за списка/дополнительного бита. Это можно несколько смягчить, если сборщик также обрабатывает выделение, поскольку тогда он может потенциально использовать неиспользуемые биты в структурах данных выделения. Или эту «скрытую память» можно устранить, используя указатель Tagged , обменивая стоимость памяти на процессорное время. Однако «отметить и смахнуть» — единственная стратегия, которая изначально легко сотрудничает с внешними распределителями.
Сборщик мусора « отметить и не зачищать» , как и «отметить и зачистить», сохраняет бит с каждым объектом для записи, является ли он белым или черным; серый набор хранится в отдельном списке или использует другой бит. Здесь есть два ключевых отличия. Во-первых, черный и белый означают разные вещи, чем в сборщике «отметить и не зачищать». В сборщике «отметить и не зачищать» все достижимые объекты всегда черные. Объект помечается черным в момент выделения, и он останется черным, даже если станет недостижимым. Белый объект — это неиспользуемая память, и ее можно выделить. Во-вторых, интерпретация черного/белого бита может измениться. Изначально черный/белый бит может иметь смысл (0=белый, 1=черный). Если операция выделения когда-либо не может найти доступную (белую) память, это означает, что все объекты помечаются как используемые (черные). Затем смысл черного/белого бита инвертируется (например, 0=черный, 1=белый). Все становится белым. Это на мгновение нарушает инвариант, что достижимые объекты являются черными, но немедленно следует фаза полной маркировки, чтобы снова пометить их черным. Как только это сделано, вся недостижимая память становится белой. Фаза "зачистки" не нужна.
Стратегия «отметить и не очищать» требует сотрудничества между распределителем и сборщиком, но невероятно эффективна с точки зрения использования памяти, поскольку требует только один бит на выделенный указатель (что в любом случае требуется большинству алгоритмов распределения). Однако этот положительный момент несколько смягчается, поскольку большую часть времени большие участки памяти ошибочно помечаются черным цветом (используются), что затрудняет возврат ресурсов системе (для использования другими распределителями, потоками или процессами) в периоды низкого использования памяти.
Таким образом, стратегию «отметить и не свивать» можно рассматривать как компромисс между плюсами и минусами стратегий «отметить и свить» и «стоп и копировать».
Эмпирически было замечено, что во многих программах самые последние созданные объекты также являются теми, которые, скорее всего, быстро станут недоступными (известно как детская смертность или гипотеза поколений ). Сборщик мусора поколений (также известный как эфемерный сборщик мусора) делит объекты на поколения и в большинстве циклов помещает только объекты подмножества поколений в начальный белый (осужденный) набор. Кроме того, система выполнения сохраняет знания о том, когда ссылки пересекают поколения, наблюдая за созданием и перезаписью ссылок. Когда запускается сборщик мусора, он может использовать эти знания, чтобы доказать, что некоторые объекты в начальном белом наборе недоступны, без необходимости проходить все дерево ссылок. Если гипотеза поколений верна, это приводит к гораздо более быстрым циклам сбора, при этом все еще возвращая большинство недоступных объектов.
Чтобы реализовать эту концепцию, многие сборщики мусора поколений используют отдельные области памяти для объектов разного возраста. Когда область заполняется, объекты в ней отслеживаются, используя ссылки из более старого поколения(й) в качестве корней. Обычно это приводит к тому, что большинство объектов в поколении собираются (согласно гипотезе), оставляя его для выделения новых объектов. Когда сборка не собирает много объектов (гипотеза не выполняется, например, потому что программа вычислила большую коллекцию новых объектов, которые она хочет сохранить), некоторые или все выжившие объекты, на которые ссылаются из более старых областей памяти, перемещаются в следующую по высоте область, и вся область затем может быть перезаписана новыми объектами. Этот метод позволяет очень быстро выполнять инкрементную сборку мусора, поскольку обычно требуется сборка мусора только одной области за раз.
Классический мусорщик поколений Ангара имеет два поколения. Он делит самое молодое поколение, называемое «новым пространством», на большой «эдем», в котором создаются новые объекты, и два меньших «пространства выживших», прошлое пространство выживших и будущее пространство выживших. Объекты в старшем поколении, которые могут ссылаться на объекты в новом пространстве, хранятся в «запомненном наборе». При каждой уборке объекты в новом пространстве отслеживаются от корней в запомненном наборе и копируются в будущее пространство выживших. Если будущее пространство выживших заполняется, объекты, которые не подходят, перемещаются в старое пространство, процесс, называемый «занятием». В конце уборки некоторые объекты находятся в будущем пространстве выживших, а Эдем и прошлое пространство выживших пусты. Затем будущее пространство выживших и прошлое пространство выживших меняются местами, и программа продолжается, распределяя объекты в Эдеме. В оригинальной системе Ангара Эдем в 5 раз больше каждого пространства выживших.
Сборка мусора на основе поколений — это эвристический подход, и некоторые недоступные объекты могут не быть возвращены в каждом цикле. Поэтому иногда может потребоваться выполнить полную маркировку и очистку или копирование сборки мусора, чтобы вернуть все доступное пространство. Фактически, системы выполнения для современных языков программирования (таких как Java и .NET Framework ) обычно используют некоторый гибрид различных стратегий, которые были описаны до сих пор; например, большинство циклов сбора могут рассматривать только несколько поколений, в то время как иногда выполняется маркировка и очистка, и еще реже выполняется полное копирование для борьбы с фрагментацией. Термины «малый цикл» и «большой цикл» иногда используются для описания этих различных уровней агрессивности сборщика.
Простые останавливающие сборщики мусора полностью останавливают выполнение программы для запуска цикла сборки, тем самым гарантируя, что новые объекты не будут выделены и объекты не станут внезапно недоступными во время работы сборщика.
Недостатком этого является то, что программа не может выполнять никакой полезной работы во время выполнения цикла сборки (иногда называемого «смущающей паузой» [6] ). Таким образом, сборка мусора Stop-the-world в основном подходит для неинтерактивных программ. Ее преимущество в том, что она проще в реализации и быстрее, чем инкрементальная сборка мусора.
Инкрементные и параллельные сборщики мусора разработаны для уменьшения этого нарушения путем чередования своей работы с деятельностью основной программы. Инкрементные сборщики мусора выполняют цикл сборки мусора дискретными фазами, при этом выполнение программы разрешено между каждой фазой (и иногда во время некоторых фаз). Конкурентные сборщики мусора вообще не останавливают выполнение программы, за исключением, возможно, короткого времени, когда сканируется стек выполнения программы. Однако сумма инкрементных фаз занимает больше времени, чем один проход пакетной сборки мусора, поэтому эти сборщики мусора могут обеспечить более низкую общую пропускную способность.
При использовании этих методов необходимо тщательное проектирование, чтобы гарантировать, что основная программа не будет мешать сборщику мусора и наоборот; например, когда программе необходимо выделить новый объект, системе выполнения может потребоваться либо приостановить ее работу до завершения цикла сбора, либо каким-то образом уведомить сборщик мусора о наличии нового достижимого объекта.
Некоторые сборщики могут правильно идентифицировать все указатели (ссылки) в объекте; они называются точными (также точными или аккуратными ) сборщиками, противоположность им — консервативные или частично консервативные сборщики. Консервативные сборщики предполагают, что любой битовый шаблон в памяти может быть указателем, если, интерпретируемый как указатель, он будет указывать на выделенный объект. Консервативные сборщики могут давать ложные срабатывания, когда неиспользуемая память не освобождается из-за неправильной идентификации указателя. На практике это не всегда проблема, если только программа не обрабатывает много данных, которые легко могут быть ошибочно идентифицированы как указатель. Ложные срабатывания, как правило, менее проблематичны в 64-битных системах, чем в 32-битных системах, поскольку диапазон допустимых адресов памяти, как правило, составляет малую часть диапазона 64-битных значений. Таким образом, произвольный 64-битный шаблон вряд ли будет имитировать допустимый указатель. Ложный отрицательный результат также может возникнуть, если указатели «скрыты», например, с помощью связанного списка XOR . Практичность точного сборщика обычно зависит от свойств безопасности типов рассматриваемого языка программирования. Примером, для которого необходим консервативный сборщик мусора, является язык C , который позволяет типизированным (непустым) указателям приводить типы к нетипизированным (пустым) указателям и наоборот.
Связанная проблема касается внутренних указателей или указателей на поля внутри объекта. Если семантика языка допускает внутренние указатели, то может быть много разных адресов, которые могут ссылаться на части одного и того же объекта, что усложняет определение того, является ли объект мусором или нет. Примером этого является язык C++ , в котором множественное наследование может привести к тому, что указатели на базовые объекты будут иметь разные адреса. В тщательно оптимизированной программе соответствующий указатель на сам объект может быть перезаписан в его регистре, поэтому такие внутренние указатели необходимо сканировать.
Производительность сборщиков мусора трассировки — как задержка, так и пропускная способность — в значительной степени зависит от реализации, рабочей нагрузки и среды. Наивные реализации или использование в средах с очень ограниченной памятью, особенно во встроенных системах, могут привести к очень низкой производительности по сравнению с другими методами, в то время как сложные реализации и использование в средах с достаточным объемом памяти могут привести к отличной производительности. [ необходима цитата ]
С точки зрения пропускной способности трассировка по своей природе требует некоторых неявных накладных расходов во время выполнения , хотя в некоторых случаях амортизированная стоимость может быть чрезвычайно низкой, в некоторых случаях даже ниже, чем одна инструкция на выделение или сбор, превосходя выделение стека. [7] Ручное управление памятью требует накладных расходов из-за явного освобождения памяти, а подсчет ссылок имеет накладные расходы из-за увеличения и уменьшения количества ссылок и проверки того, переполнился ли счетчик или упал до нуля.
С точки зрения задержки простые сборщики мусора stop-the-world приостанавливают выполнение программы для сборки мусора, которая может происходить в произвольное время и занимать произвольно много времени, что делает их непригодными для вычислений в реальном времени , особенно для встроенных систем, и плохо подходят для интерактивного использования или любой другой ситуации, где низкая задержка является приоритетом. Однако инкрементные сборщики мусора могут обеспечить жесткие гарантии реального времени, а в системах с частым временем простоя и достаточным количеством свободной памяти, таких как персональные компьютеры, сбор мусора может быть запланирован на время простоя и иметь минимальное влияние на интерактивную производительность. Ручное управление памятью (как в C++) и подсчет ссылок имеют схожую проблему произвольно длинных пауз в случае освобождения большой структуры данных и всех ее дочерних элементов, хотя они происходят только в фиксированное время, независимо от сборки мусора.
Трудно сравнивать два случая напрямую, так как их поведение зависит от ситуации. Например, в лучшем случае для системы сбора мусора выделение просто увеличивает указатель, но в лучшем случае для ручного выделения кучи распределитель поддерживает свободные списки определенных размеров, и выделение требует только следования указателю. Однако такое разделение размеров обычно вызывает большую степень внешней фрагментации, что может оказать неблагоприятное влияние на поведение кэша. Выделение памяти в языке сбора мусора может быть реализовано с использованием выделения кучи за кулисами (а не простого увеличения указателя), поэтому перечисленные выше преимущества производительности не обязательно применимы в этом случае. В некоторых ситуациях, особенно во встроенных системах , можно избежать накладных расходов как на сборку мусора, так и на управление кучей, предварительно выделяя пулы памяти и используя пользовательскую, легковесную схему для выделения/освобождения. [8]
Издержки, связанные с барьерами записи, скорее всего, будут заметны в программе императивного стиля, которая часто записывает указатели в существующие структуры данных, чем в программе функционального стиля, которая создает данные только один раз и никогда не изменяет их.
Некоторые достижения в сборке мусора можно рассматривать как реакцию на проблемы производительности. Ранние сборщики мусора были сборщиками stop-the-world, но производительность этого подхода отвлекала в интерактивных приложениях. Инкрементный сборщик избежал этого нарушения, но ценой снижения эффективности из-за необходимости барьеров. Методы сбора поколений используются как с stop-the-world, так и с инкрементными сборщиками для повышения производительности; компромисс заключается в том, что некоторый мусор не обнаруживается как таковой дольше, чем обычно.
Хотя сборка мусора в целом недетерминирована, ее можно использовать в жестких системах реального времени . Сборщик мусора в реальном времени должен гарантировать, что даже в худшем случае он выделит определенное количество вычислительных ресурсов потокам мутатора. Ограничения, налагаемые на сборщик мусора в реальном времени, обычно основаны либо на работе, либо на времени. Ограничение на основе времени будет выглядеть так: в каждом временном окне продолжительности потоки мутатора должны работать по крайней мере в течение времени. Для анализа на основе работы MMU (минимальное использование мутатора) [9] обычно используется в качестве ограничения в реальном времени для алгоритма сборки мусора.
Одна из первых реализаций жесткой сборки мусора в реальном времени для JVM была основана на алгоритме Metronome, [10] коммерческая реализация которого доступна как часть IBM WebSphere Real Time . [11] Другим жестким алгоритмом сборки мусора в реальном времени является Staccato, доступный в J9 JVM от IBM , который также обеспечивает масштабируемость для больших многопроцессорных архитектур, принося при этом различные преимущества по сравнению с Metronome и другими алгоритмами, которые, напротив, требуют специализированного оборудования. [12]
Одной из основных проблем для сбора мусора в реальном времени на современных многоядерных архитектурах является проектирование неблокируемой параллельной сборки мусора, не позволяющей параллельным потокам блокировать друг друга и создавать непредсказуемые паузы. Исследование алгоритмов, которые позволяют выполнять неблокируемую параллельную сборку мусора в реальном времени, представлено в статье Pizlo et al. в Microsoft Research. [13]