Теория языков программирования

Сборка мусора

Каждый язык должен решить: кто освобождает память? C/C++ - программист (и это источник 70% CVE). Rust - compile-time borrow checker. Java/Go/Python - garbage collector. Каждый выбор имеет цену: продуктивность, производительность, паузы.

  • **Go GC**: concurrent tri-color mark with sub-millisecond STW. Discord перешёл с Python на Go ради предсказуемых latencies - GC паузы были главной проблемой Python при 5M соединениях
  • **Java ZGC**: Google использует ZGC в Android runtime для smooth 120fps - <1ms GC паузы критичны для gaming
  • **Python ARC**: CPython использует reference counting + cyclic GC для циклов. Это причина GIL - thread-safe инкремент/декремент RC без mutex требует GIL

Mark-Sweep GC

Mark-sweep - базовый алгоритм GC из двух фаз. Mark: обходим граф объектов от GC roots (стек, глобальные переменные, регистры) и помечаем живые объекты. Sweep: проходим всю heap и освобождаем непомеченные объекты.

Mark-sweep имеет фрагментацию heap. Mark-compact решает это: после sweep, живые объекты компактируются в начало heap. Цена: дополнительный проход и обновление всех ссылок.

Что такое GC roots и почему обход начинается с них?

Generational GC

Generational hypothesis: большинство объектов умирают молодыми. Generational GC делит heap на поколения (generations). Young generation - маленькая, собирается часто (minor GC). Old generation - большая, собирается редко (major GC). JVM, .NET, Python используют generational GC.

Write barrier - ключевой механизм: при записи ссылки из Old в Young объект, GC записывает это в remembered set. Иначе minor GC не нашёл бы Young объекты, достижимые только из Old.

Почему generational GC быстрее simple mark-sweep?

Concurrent GC

Concurrent GC работает параллельно с программой, уменьшая pause times. Проблема: программа мутирует граф объектов во время mark. Tri-color marking (белый/серый/чёрный) + write barriers решают это. ZGC (Java), Shenandoah, Go GC - примеры.

Зачем concurrent GC нужны write barriers?

Reference Counting

Reference counting (RC) - каждый объект хранит счётчик ссылок. При добавлении ссылки - инкремент, при удалении - декремент. При нуле - освобождение. Детерминированное освобождение, нет GC пауз. Python, Swift, Rust Arc<T> используют RC.

GC паузы - главный недостаток Java. JVM не подходит для real-time систем

ZGC (Java 15+) имеет паузы <1ms при любом размере heap. Современный JVM пригоден для soft real-time

ZGC, Shenandoah, G1 - результат 15 лет работы. Проблема не в GC паузах, а в настройке JVM и выборе collector

Почему простой reference counting не может собрать циклические структуры?

Итоги

  • **Mark-sweep**: обход от roots -> mark, sweep heap -> free unmarked. Stop-the-world. Фрагментация решается compact
  • **Generational**: young (частый, быстрый) + old (редкий, дорогой). Write barrier для remembered set. JVM/CLR/Python
  • **Concurrent GC**: tri-color marking параллельно с программой. Write barriers сохраняют инвариант. ZGC: <1ms при 16TB
  • **Reference counting**: детерминированное освобождение, нет пауз, но циклы - утечка. Weak references для разрыва. Swift ARC, Rust Arc

Связанные темы

GC тесно связан с runtime и языком:

  • Модели памяти — Concurrent GC требует аккуратной работы с memory model для write barriers
  • WebAssembly — Wasm GC proposal добавляет GC в WebAssembly - влияет на компиляцию managed языков в Wasm

Вопросы для размышления

  • Rust использует borrow checker вместо GC и гарантирует memory safety без runtime overhead. Почему не все языки переходят на этот подход?
  • Python GIL существует частично из-за non-thread-safe reference counting. Как Py-nogil (PEP 703) планирует решить эту проблему без потери совместимости?
  • ZGC достигает <1ms пауз через coloured pointers и load barriers. Какую цену платит программа за это в терминах throughput?

Связанные уроки

  • alg-12-bfs
Сборка мусора

0

1

Войти