Перейти к основному содержимому

Сборка мусора — итоги

Разработчику Аналитику Тестировщику Архитектору Инженеру

Кратко — что стоит унести из раздела "Сборка мусора". Если пункт кажется туманным — откройте указанную главу или оглавление.


FAQ — Часто задаваемые вопросы

Типичные сбои и ситуации при работе с памятью и GC — от "память растёт" до пауз в prod. Здесь — что делать и где копать в главах; формулировки для самопроверки — в чек-листе.

Вопрос. "У меня GC — значит утечек памяти быть не может?"

Ответ. GC освобождает только недостижимые объекты. Если вы держите ссылку в static-списке, кэше или обработчике события — объект жив и память не вернётся. Подробнее здесь — автоматическое управление памятью.

Вопрос. Приложение со временем съедает всю RAM, после перезапуска снова нормально.

Ответ. Классика логической утечки — накопление сессий, подписок, записей в кэше без TTL. Снимите heap dump и найдите цепочку удерживающих ссылок. Подробнее здесь — теория и утечки, Java, Python и Go.

Вопрос. OutOfMemoryError в Java, хотя "на сервере 32 ГБ".

Ответ. У JVM свой лимит -Xmx — он может быть 512 МБ при большой машине. Поднимите heap осознанно после профилирования, а не "наугад". Подробнее здесь — шпаргалка Java, Python и Go, JVM.

Вопрос. UI "замирает" на секунду — в логах Full GC.

Ответ. Это stop-the-world пауза — сборщик останавливает приложение. Уменьшайте давление на heap, настройте G1/ZGC, уберите аллокационные пики. Подробнее здесь — автоматическое управление памятью, сравнение GC.

Вопрос. Вызвал GC.Collect() / System.gc() — стало лучше. Добавить в hot path?

Ответ. Принудительный GC — крайняя мера для диагностики или узких сценариев; в цикле запросов ухудшит latency. Чините удержание объектов, а не "вызывайте мусорщика чаще". Подробнее здесь — автоматическое управление памятью.

Вопрос. Подписался на событие в C#, отписаться забыли — форма закрыта, память не освобождается.

Ответ. Делегат держит ссылку на подписчика — типичная утечка в UI. Отписывайтесь в Dispose / OnClosed или используйте weak events. Подробнее здесь — автоматическое управление памятью.

Вопрос. Static List<T> для "временного" кэша — через неделю prod падает.

Ответ. Static живёт до завершения процесса — это корень достижимости. Добавьте TTL, LRU или вынесите кэш во внешнее хранилище с лимитом. Подробнее здесь — теория.

Вопрос. Python: циклические ссылки между объектами — del не помогает сразу.

Ответ. Подсчёт ссылок не видит циклы — их подбирает модуль gc. Разрывайте циклы явно или используйте weakref. Подробнее здесь — CPython, шпаргалка.

Вопрос. gc.disable() в Python "для скорости" на prod — нормальная практика?

Ответ. Опасно — циклический мусор не соберётся, память вырастет. Отключение только в коротких измеренных бенчмарках с пониманием риска. Подробнее здесь — шпаргалка Java, Python и Go.

Вопрос. Go: память в top не падает после обработки запроса — утечка?

Ответ. Go часто не возвращает память ОС сразу — смотрите runtime.MemStats, pprof heap, а не только RSS. Рост без plateau — повод для dump. Подробнее здесь — Go runtime, шпаргалка.

Вопрос. Большие объекты (>85 КБ в .NET) лежат в LOH — фрагментация и паузы.

Ответ. LOH не compactится по умолчанию в старых версиях — используйте ArrayPool, пулы буферов, меньшие чанки. Подробнее здесь — автоматическое управление памятью.

Вопрос. Server GC vs Workstation — поставил Server, desktop-приложение стало дёргаться.

Ответ. Server GC оптимизирован под throughput на сервере, не под интерактивный UI. Для клиента — Workstation или настройка latency mode. Подробнее здесь — теория .NET GC, шпаргалка.

Вопрос. Java: переключил на ZGC, latency лучше, но CPU вырос на 20%.

Ответ. Низкие паузы — trade-off с накладными расходами. Выбирайте GC под SLA (latency vs throughput). Подробнее здесь — шпаргалка Java, Python и Go, JVM.

Вопрос. using закрыли, а native handle всё ещё держит файл.

Ответ. IDisposable освобождает управляемую обёртку; финализатор может сработать позже. Явный Dispose и using для файлов, сокетов, GDI. Подробнее здесь — автоматическое управление памятью.

Вопрос. Профiler показывает много Gen0 collections — это плохо?

Ответ. Частые Gen0 при короткоживущих объектах — норма. Проблема — promotion в Gen2 и частые Full GC. Снижайте аллокации в hot path. Подробнее здесь — поколенческая модель, шпаргалка.

Вопрос. Allocation failure в Java логах каждые пять минут.

Ответ. Куча заполнена или GC не успевает — нужен heap dump, а не только увеличение -Xmx. Часто виноват leak или слишком маленький heap под нагрузкой. Подробнее здесь — шпаргалка.

Вопрос. Долгоживущий Python-процесс (Celery worker) — память ползёт вверх месяцами.

Ответ. Фрагментация pymalloc, накопление interned строк, циклы — планируйте rolling restart и мониторинг, параллельно ищите leak через tracemalloc. Подробнее здесь — CPython, теория.

Вопрос. GOGC=off на Go-сервисе — память стабильна, можно так в prod?

Ответ. Без GC куча растёт до OOM — off только для кратких экспериментов. Настраивайте GOGC по метрикам, смотрите GODEBUG=gctrace=1. Подробнее здесь — Go runtime, шпаргалка.

Вопрос. Finalizer в Java/.NET "дополнительно" чистит ресурс — надёжно?

Ответ. Финализация не детерминирована — файлы и соединения закрывайте в try-with-resources / using. Finalizer — страховка, не основной путь. Подробнее здесь — автоматическое управление памятью.

Вопрос. Пул объектов — когда имеет смысл, а когда усложняет код зря?

Ответ. Имеет смысл при частом создании тяжёлых объектов (буферы, строки builder). Для мелких short-lived объектов пул часто проигрывает аллокатору + GC. Подробнее здесь — теория.

Вопрос. Eclipse MAT / dotMemory показывают "retained heap 500 МБ" — с чего начать анализ?

Ответ. Откройте dominator tree, найдите крупнейший retained object, пройдите path to GC roots — кто держит. Сравните два dump до/после нагрузки. Подробнее здесь — автоматическое управление памятью.

Вопрос. Контейнер Kubernetes убивает pod по OOMKilled — JVM ещё "думает", что памяти достаточно.

Ответ. Лимит cgroup ниже -Xmx + native memory (metaspace, threads, direct buffers). Выставьте -Xmx с запасом под контейнер или используйте Container Support в JVM. Подробнее здесь — шпаргалка, JVM.

Вопрос. WeakReference / SoftReference — "сделаю кэш на weak и забуду".

Ответ. Weak не гарантирует hit rate — GC может собрать в любой момент. Soft в JVM выживает дольше при нехватке памяти. Кэш проектируйте с явным TTL и лимитом. Подробнее здесь — теория, Java.

Вопрос. После апгрейда JDK приложение стало GC-ить в два раза чаще — "новый JDK хуже"?

Ответ. Сменился сборщик по умолчанию (например, G1 вместо Parallel) — перечитайте release notes, подберите флаги или вернитесь к явной настройке. Подробнее здесь — шпаргалка Java, Python и Go.

Вопрос. Новичок пишет на C++ "потому что там нет GC и не будет утечек".

Ответ. Без GC ответственность на программисте — забытый delete и dangling pointer тоже "утечки". GC-языки переносят проблему в логическое удержание ссылок. Подробнее здесь — теория, C++.

Вопрос. Мониторинг показывает рост памяти, но после Full GC всё возвращается — panic зря?

Ответ. Если после GC память стабильно возвращается к baseline — это высокое потребление, не обязательно leak. Leak — монотонный рост baseline после каждого цикла GC. Подробнее здесь — автоматическое управление памятью, чек-лист.

Вопрос. AsyncLocal / ThreadLocal в Java — "забыли remove", thread pool переиспользует потоки.

Ответ. Значение остаётся на потоке и удерживает объект между запросами. Всегда remove() в finally в pooled threads. Подробнее здесь — теория и утечки, JVM.

Вопрос. Что такое сборка мусора (garbage collection) в программировании?

Ответ. GC — автоматическое освобождение памяти от недостижимых объектов в Java, C#, Python, Go и др. Программист не вызывает free вручную. Подробнее здесь — автоматическое управление памятью.

Вопрос. Утечка памяти в Java при наличии GC — возможна ли?

Ответ. Да, если объект логически не нужен, но на него есть ссылка (static map, listener, cache). GC не удалит "живое" по его мнению. Подробнее здесь — теория и утечки, JVM.

Вопрос. OutOfMemoryError Java — как исправить и что проверить?

Ответ. Heap dump, увеличение -Xmx только после анализа, поиск утечки или слишком больших объектов в MAT/VisualVM. Подробнее здесь — автоматическое управление памятью, шпаргалка Java, Python и Go.

Вопрос. G1 GC vs ZGC vs Shenandoah — какой выбрать в Java?

Ответ. G1 — универсальный default; ZGC/Shenandoah — низкие паузы на больших heap. Выбор по SLA latency и версии JDK. Подробнее здесь — шпаргалка, JVM.

Вопрос. GC.Collect() в C# — когда можно вызывать?

Ответ. Почти никогда в application code — только диагностика или узкие API. Полагайтесь на runtime .NET. Подробнее здесь — автоматическое управление памятью.

Вопрос. IDisposable и using в C# — зачем, если есть GC?

Ответ. GC не сразу закрывает файлы, сокеты, handlesusing вызывает Dispose детерминированно. Подробнее здесь — автоматическое управление памятью.

Вопрос. Утечка памяти Python — как найти через gc и tracemalloc?

Ответ. tracemalloc, objgraph, модуль gc.get_referrers — ищите цепочку ссылок. Циклы ловит cyclic GC. Подробнее здесь — CPython, теория.

Вопрос. Почему Python не освобождает память сразу после del?

Ответ. del убирает одну ссылку; объект жив, пока refcount > 0 или цикл не собрал cyclic GC. pymalloc может не отдавать память ОС. Подробнее здесь — CPython, шпаргалка.

Вопрос. Go garbage collector — как работает GOGC?

Ответ. GOGC=100 (default) — GC когда live heap удвоился; больше значение — реже GC, больше RAM. Смотрите GODEBUG=gctrace=1. Подробнее здесь — Go runtime, шпаргалка.

Вопрос. Stop-the-world pause — что это в JVM и .NET?

Ответ. STW — момент, когда все потоки приложения остановлены для mark/sweep. Цель современных GC — сократить паузы. Подробнее здесь — автоматическое управление памятью.

Вопрос. Поколенческая гипотеза (generational GC) — простыми словами?

Ответ. Большинство объектов умирают молодыми — GC чаще собирает Gen0/Young, реже Old. Экономит время. Подробнее здесь — теория, шпаргалка.

Вопрос. Large Object Heap (LOH) в .NET — что нужно знать?

Ответ. Объекты ≥ ~85 КБ идут в LOH; фрагментация и редкая компакция — используйте пулы буферов. Подробнее здесь — автоматическое управление памятью.

Вопрос. Как настроить размер heap Java — -Xms и -Xmx?

Ответ. -Xms — начальный размер, -Xmx — максимум; часто ставят equal для серверов, чтобы избежать resize. Подробнее здесь — шпаргалка Java, Python и Go.

Вопрос. Memory leak vs high memory usage — как отличить?

Ответ. Leak — baseline RAM растёт после каждого GC; high usage — много данных, но plateau стабилен. Подробнее здесь — теория, чек-лист.

Вопрос. WeakReference и SoftReference в Java — когда использовать?

Ответ. Weak — не мешать GC; Soft — кэш, выживающий до нехватки памяти. Для prod-кэша лучше Caffeine с TTL. Подробнее здесь — теория, JVM.

Вопрос. Finalizer vs try-with-resources в Java — что deprecated?

Ответ. Finalizers ненадёжны и deprecated; для файлов и БД — try-with-resources / AutoCloseable. Подробнее здесь — автоматическое управление памятью.

Вопрос. Как профилировать память .NET — dotMemory, PerfView?

Ответ. Снимите snapshot, dominators, сравните до/после нагрузки; dotnet-counters для live GC metrics. Подробнее здесь — теория.

Вопрос. Mark-and-sweep vs reference counting — в чём разница?

Ответ. Reference counting (CPython base) не видит циклы; mark-and-sweep обходит от корней — стандарт для JVM/.NET/Go. Подробнее здесь — теория, шпаргалка.

Вопрос. Утечка памяти в JavaScript / Node.js — бывает ли без GC?

Ответ. V8 имеет GC, но глобальные массивы, closures, timers удерживают объекты — аналог логической утечки. Подробнее здесь — теория достижимости, JavaScript.

Вопрос. Serial GC vs Parallel GC vs G1 — для какого приложения?

Ответ. Serial — tiny apps; Parallel — throughput batch; G1/ZGC — сервисы с требованиями к latency. Подробнее здесь — шпаргалка Java, Python и Go.

Вопрос. Kubernetes OOMKilled Java pod — как настроить память?

Ответ. Лимит контейнера должен покрывать -Xmx + metaspace + native + threads; используйте Container Support flags в JDK. Подробнее здесь — шпаргалка, теория.

Вопрос. Нужно ли изучать GC, если пишу на Python/Java "просто код"?

Ответ. Базовое понимание помогает при prod инцидентах и утечках — не нужно писать свой GC, но знать roots и retainers полезно. Подробнее здесь — о разделе, теория.

Вопрос. Object pool (пул объектов) — когда помогает снизить нагрузку на GC?

Ответ. При частом создании тяжёлых mutable буферов (byte[], StringBuilder) — переиспользование снижает аллокации. Подробнее здесь — автоматическое управление памятью.

Вопрос. Сравнение GC Java Python Go — краткая таблица?

Ответ. Java — generational collectors; Python — refcount + cycles; Go — concurrent mark-sweep, GOGC. Подробнее здесь — Java, Python и Go — шпаргалка.


Что запомнить

Автоматическое управление памятью — неотъемлемая часть современных языков программирования, обеспечивающая безопасность и удобство разработки. Сборщик мусора (Garbage Collector, GC) берёт на себя задачу освобождения недостижимых объектов, позволяя программисту сосредоточиться на логике приложения. Однако автоматизация не отменяет необходимости понимания внутреннего устройства GC.

В .NET используется поколенческая модель с фоновой сборкой, режимами задержки и возможностью настройки через конфигурацию или API. В Java представлено несколько реализаций GC (G1, ZGC, Shenandoah), каждая из которых оптимизирована под определённые сценарии: пропускную способность, низкие задержки или масштабируемость. В Python основной механизм — подсчёт ссылок, дополненный циклическим сборщиком для обнаружения замкнутых графов недостижимых объектов. В Go — конкурентный mark-and-sweep без поколений кучи, с трёхцветной разметкой и write barrier; сравнение трёх языков — в шпаргалке Java, Python и Go.

Несмотря на различия в реализации, все три экосистемы сталкиваются с одной и той же проблемой — утечка памяти возникает не из-за ошибок GC, а из-за сохранения логически ненужных, но технически достижимых ссылок. Это может быть статический список, обработчик события, кэш без TTL или неудалённый подписчик. GC работает корректно — он видит живую ссылку и удерживает объект. Ответственность за очистку таких ссылок лежит на разработчике.

Эффективное управление памятью требует:

  • проектирования жизненного цикла объектов;
  • явного удаления ссылок при завершении работы с ресурсом;
  • использования инструментов профилирования (Visual Studio, JFR, tracemalloc);
  • понимания поведения GC в конкретной среде выполнения;
  • применения шаблонов (Dispose, AbortController, пулы объектов).

Принудительный вызов GC — крайняя мера, допустимая только в контролируемых условиях. Настройка GC — инструмент для достижения предсказуемости, а не способ компенсировать плохую архитектуру. Главный принцип: GC управляет памятью, но не управляет семантикой приложения.


Куда идти дальше

Полный маршрут — на странице о разделе.

Проверьте себя: Чек-лист самопроверки.