Сборка мусора в Go
Трёхцветный mark-sweep сборщик, write-барьеры, темп GC и запуск сборки.
8 вопросов
JuniorТеорияОчень частоКакой сборщик мусора использует Go и каковы его определяющие свойства?
Какой сборщик мусора использует Go и каковы его определяющие свойства?
Go использует конкурентный трёхцветный mark-and-sweep сборщик мусора. Он не поколенческий и не уплотняющий — объекты не перемещаются, указатели стабильны. Mark и sweep идут параллельно программе.
Типичные ошибки
- ✗Называть сборщик Go поколенческим — в Go нет молодого и старого поколений
- ✗Считать, что сборщик уплотняет кучу и перемещает живые объекты
- ✗Путать mark-and-sweep с подсчётом ссылок
Уточняющие вопросы
- →Почему неуплотняющий сборщик рискует фрагментацией кучи и как Go её ограничивает?
- →Что означает «трёхцветный» в mark-and-sweep сборщике Go?
JuniorТеорияЧастоЧто такое паузы stop-the-world и когда они возникают в GC Go?
Что такое паузы stop-the-world и когда они возникают в GC Go?
Пауза stop-the-world останавливает все goroutine, чтобы рантайм работал с согласованной кучей. Современный Go имеет лишь две короткие STW-паузы за цикл — на старте mark и при завершении mark — обычно меньше миллисекунды. Сами mark и sweep работают конкурентно.
Типичные ошибки
- ✗Считать, что Go останавливает мир на всю фазу mark, а не на две короткие паузы
- ✗Полагать, что длина STW-паузы растёт с размером живой кучи в современном Go
- ✗Считать, что у GC Go нет пауз — короткие STW-паузы есть на границах цикла
Уточняющие вопросы
- →Почему завершение mark всё ещё STW-пауза, если mark конкурентен?
- →Как рантайм останавливает все goroutine, если не все они в safepoint?
JuniorТеорияИногдаКак запускается цикл GC и что делают GOGC и runtime.GC?
Как запускается цикл GC и что делают GOGC и runtime.GC?
Цикл начинается, когда куча выросла на GOGC процентов с прошлого цикла — при значении по умолчанию GOGC=100 куча примерно удваивается. runtime.GC() принудительно запускает цикл и блокируется до его конца. Цикл также форсируется, если GC не было две минуты.
Типичные ошибки
- ✗Считать
GOGCинтервалом времени, а не процентом роста кучи - ✗Полагать, что GC запускается по фиксированному таймеру, а не от аллокаций
- ✗Считать
runtime.GC()асинхронным — он блокируется до конца цикла
Уточняющие вопросы
- →Как
GOMEMLIMITменяет момент запуска цикла? - →Что такое GC-пейсер и как он решает, когда запустить следующий цикл?
MiddleТеорияИногдаЧто решает GC-пейсер и на основе чего он принимает эти решения?
Что решает GC-пейсер и на основе чего он принимает эти решения?
Пейсер решает, когда начать следующий цикл и сколько mark-assist должны выполнять аллоцирующие goroutine. Он оценивает темп аллокаций и mark, чтобы конкурентный mark завершился, когда куча достигнет цели от GOGC.
Типичные ошибки
- ✗Считать, что пейсер управляет числом потоков сборщика, а не таймингом цикла и assist
- ✗Полагать, что пейсер переупорядочивает сканирование объектов ради ускорения цикла
- ✗Считать задачей пейсера ограничение длины STW-паузы
Уточняющие вопросы
- →Как меняется поведение пейсера, когда
GOMEMLIMITблизок к размеру живой кучи? - →Как пейсер вычисляет счётчик assist
долг mark-assistна каждую goroutine?
MiddleТеорияИногдаКак работает трёхцветный алгоритм mark-sweep в сборщике мусора Go?
Как работает трёхцветный алгоритм mark-sweep в сборщике мусора Go?
Объекты белые (кандидаты в мусор), серые (достижимые, но не просканированные) или чёрные (достижимые и просканированные). Mark стартует от корней, переводит серые в чёрные, окрашивая их ссылки в серый, и завершается, когда серых нет. Белые объекты затем выметаются sweep.
Типичные ошибки
- ✗Путать цвета — считать, что чёрный это мусор, а белый — живой объект
- ✗Принимать цвета за возраст объектов, а не прогресс сканирования в одном цикле
- ✗Забывать инвариант: ни один чёрный объект не должен ссылаться на белый
Уточняющие вопросы
- →Что такое трёхцветный инвариант и что его нарушает при конкурентном mark?
- →Почему белый объект можно безопасно освободить, когда не осталось серых?
MiddleТеорияИногдаGo помечает кучу конкурентно, пока программа работает. Зачем нужен барьер записи для корректности?
Go помечает кучу конкурентно, пока программа работает. Зачем нужен барьер записи для корректности?
Пока идёт mark, мьютатор может записать указатель в уже просканированный чёрный объект, спрятав белый объект от сборщика. Барьер записи перехватывает запись указателей и окрашивает затронутый объект в серый, сохраняя инвариант: чёрный объект не достигает белого.
Типичные ошибки
- ✗Считать барьер записи блокировкой битов цвета, а не хуком окрашивания
- ✗Полагать, что барьер нужен для перемещения указателей — сборщик Go не двигает объекты
- ✗Считать, что конкурентный mark корректен без всякого участия мьютатора
Уточняющие вопросы
- →Чем гибридный алгоритм барьера записи Дейкстры-Юаса в Go отличается от простого барьера вставки?
- →Почему стек можно оставить без серых объектов без барьера на запись в стек?
SeniorТеорияИногдаКак Go выполняет mark конкурентно с мьютатором, включая троттлинг аллокатора mark assist?
Как Go выполняет mark конкурентно с мьютатором, включая троттлинг аллокатора mark assist?
Фоновые mark-воркеры сканируют серые объекты, пока программа работает; барьер записи держит трёхцветный инвариант при конкурентной записи указателей. Если аллокация обгоняет mark, пейсер начисляет каждой аллоцирующей goroutine пропорциональный mark-assist.
Типичные ошибки
- ✗Путать барьер записи с барьером чтения — Go использует барьер записи
- ✗Считать, что mark assist порождает воркеры, а не нагружает саму аллоцирующую goroutine
- ✗Полагать, что mark assist — это STW-пауза, а не работа, выполняемая мьютатором
Уточняющие вопросы
- →Почему начисление mark assist аллокатору делает GC самостабилизирующимся?
- →Как рантайм делит CPU между фоновыми mark-воркерами и мьютатором?
SeniorТеорияРедкоКак настроить GC под задержку или пропускную способность через GOMEMLIMIT и ballast?
Как настроить GC под задержку или пропускную способность через GOMEMLIMIT и ballast?
Повышение GOGC даёт меньше циклов — больше пропускной способности и памяти; понижение наоборот. GOMEMLIMIT (Go 1.19+) задаёт мягкий потолок памяти, собирая агрессивнее у его границы. Старый трюк ballast теперь вытеснен GOMEMLIMIT.
Типичные ошибки
- ✗Путать направление
GOGC— большийGOGCозначает меньше циклов, а не больше - ✗Считать
GOMEMLIMITжёстким лимитом с паникой, а не мягкой целью пейсинга - ✗Советовать трюк ballast вместо
GOMEMLIMITна современном Go
Уточняющие вопросы
- →Что будет, если
GOMEMLIMITзадан так низко, что одна живая куча его превышает? - →Почему ballast работал до
GOMEMLIMITи в чём был его главный недостаток?