Теория языков программирования
Сборка мусора
Каждый язык должен решить: кто освобождает память? 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?