Рецепт медленного GC: остановить все потоки, обойти _весь_ граф живых объектов, пройтись по всем аллоцированным объектам, сделав непомеченные свободными, затем возобновить все потоки дальше. И делать так _каждый раз_.
Между двумя вызовами GC можно с указателями делать что хочешь, при следующей сборке они все будут анализироваться заново.
Рецепт GC с короткими паузами: за один раз обходить и освобождать лишь небольшой кусок памяти, например маленькое нулевое поколение или фрагмент поколения побольше. Идеально делать это параллельно основой программе, не останавливая ее.
Но тогда при следующем вызове GC у нас должны быть гарантии, что объект, который мы уже пометили раньше, не изменился и не приобрел ссылки на новые объекты. Если недавно был аллоцирован новый объект, и единственная ссылка на него помещена в какой-то старый, куда GC еще раз не посмотрит, то мы этот новый объект ошибочно сочтем за мусор и освободим.
Нужен механизм, который бы сообщал GC о том, что уже обработанный и помеченный объект изменился и поимел ссылки на что-то новое. Для этого обычно используют write barriers, небольшой кусок логики, отрабатывающий при каждой записи в поле объекта в GC-куче. Или, реже и еще хуже, read barriers, когда каждое чтение поля из объекта в GC-куче сопровождается дополнительной логикой.
В обоих случаях это не делается библиотеками, это компилятор должен вставлять в код, в каждую запись указателя в объект.
Но D такие вещи делать не будет, он опирается на совместимость с Си и отчасти С++, с указателями обращается почти так же вольно, как С/С++, обменивается с ними (компилятор не знает, где имеет дело с объектом из GC кучи, а где с объектом из сишной библиотеки, выделенным маллоком). Из-за такого неразделения указателей на управляемые и неуправляемые (как делали в С++/CLI, например), ни о каких write barriers речи идти не может, а потому и GC с поколениями и/или инкрементальностью сделать не выйдет. А без них он обречен обходить всю память каждый раз, что очень медленно.