Логотип Workflow

Article

Memory And Garbage Collector

Тема 6. Memory and Garbage Collector

Память в Java это фундамент производительности и стабильности. Большая часть проблем в продакшене связана не с синтаксисом, а с лишними ссылками, неверной моделью жизненного цикла объектов и неправильными ожиданиями от Garbage Collector.

Stack and Heap

Базовая модель памяти

stack и heap решают разные задачи:

  • stack хранит кадры вызовов методов: локальные переменные и ссылки;
  • heap хранит сами объекты и массивы;
  • после завершения метода его stack frame удаляется автоматически;
  • объект в heap живет, пока достижим по ссылкам.
int age = 30;         // примитивное значение
String name = "Alex"; // ссылка на объект в heap

Как работает Garbage Collector

GC очищает память по достижимости (reachability), а не по таймеру.

Упрощенный алгоритм:

  1. JVM определяет корневые точки (GC Roots): активные потоки, локальные переменные в стеке, статические поля и т.д.
  2. От корней проходит граф ссылок и помечает достижимые объекты как живые.
  3. Недостижимые объекты считаются мусором и могут быть удалены.
  4. При необходимости память уплотняется, чтобы снизить фрагментацию.

Garbage Collector

Ключевой вывод: вы не контролируете точный момент удаления объекта. System.gc() только просьба к JVM, а не команда с гарантией.

Поколенческая модель памяти

Большинство современных GC в JVM опираются на идею поколений:

  • Young Generation: сюда попадают новые объекты;
  • Old Generation: долгоживущие объекты, пережившие несколько сборок;
  • Metaspace: метаданные классов (не сами объекты).

Почему это работает: в типичном приложении много короткоживущих объектов, и их выгодно собирать чаще и быстрее.

Виды GC-пауз простыми словами

  • Minor GC: чаще, обычно короче, работает в young generation.
  • Major/Full GC: реже, обычно тяжелее, может затрагивать old generation и давать заметные паузы.

Если приложение начинает «подвисать», часто причина в слишком частых или слишком долгих паузах GC.

Частые причины утечек памяти в Java

Утечка в Java это ситуация, когда GC исправен, но объекты остаются достижимыми из-за ошибочной логики ссылок.

Основные паттерны:

  1. static коллекции, которые бесконечно растут.
  2. Кеш без лимита и без стратегии удаления.
  3. Подписки/слушатели без отписки.
  4. ThreadLocal без очистки в долгоживущих потоках.
  5. Хранение тяжелых объектов в singleton-компонентах без необходимости.

Пример:

public class CacheHolder {
    private static final List<byte[]> CACHE = new ArrayList<>();

    public static void add(byte[] data) {
        CACHE.add(data); // рост без ограничений
    }
}

Здесь данные остаются достижимыми через CACHE, поэтому GC не имеет права их удалить.

Как писать код, дружественный к GC

  1. Ограничивайте размер кешей и очередей.
  2. Освобождайте подписки и ресурсы явно (close, unsubscribe).
  3. Не держите ненужные ссылки в долгоживущих объектах.
  4. Избегайте создания лишних временных объектов в горячих циклах.
  5. Для крупных буферов продумывайте жизненный цикл заранее.

Мини-практика для диагностики

  1. Следите за графиками heap usage и GC pause time.
  2. При росте памяти снимайте heap dump и ищите «кто держит ссылку».
  3. Проверяйте dominator tree: какие объекты удерживают наибольший объем.
  4. Сверяйте поведение с нагрузкой: утечка обычно проявляется как «ступенчатый рост» памяти без возврата к базовому уровню.

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

  • GC удаляет только недостижимые объекты.
  • Проблемы памяти чаще вызваны архитектурой ссылок, а не «плохим GC».
  • Контроль памяти это часть проектирования данных, кешей и жизненного цикла компонентов.

Quiz

Проверьте, что вы усвоили

Авторизуйтесь чтоб пройти тесты