← Назад к вопросам

Как очищается память в JVM

1.8 Middle🔥 191 комментариев
#JVM и управление памятью

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как очищается память в JVM

Управление памятью в Java одно из главных преимуществ языка перед С++ и С. Автоматическая очистка памяти через Garbage Collection (GC) — фундаментальный механизм JVM. Рассмотрю детально, как это работает.

1. Структура памяти JVM

Память JVM разделяется на несколько областей:

JVM Heap Memory
├── Young Generation (Молодое поколение)
│   ├── Eden Space (80%)
│   ├── Survivor Space 0 (10%)
│   └── Survivor Space 1 (10%)
└── Old Generation (Старое поколение)

Отдельно:
├── Method Area (Metaspace)
└── Stack (для каждого потока)

Young Generation:

  • Где создаются новые объекты
  • Сборка мусора здесь очень частая и быстрая
  • После пережившие объекты переходят в Old Generation

Old Generation:

  • Долгоживущие объекты
  • Сборка мусора здесь реже, но дольше

2. Жизненный цикл объекта в памяти

Шаг 1: Рождение объекта

User user = new User("John");  // Объект создаётся в Eden Space

Когда вы создаёте объект, JVM:

  1. Выделяет память в Eden Space (Young Generation)
  2. Инициализирует переменные по умолчанию
  3. Вызывает конструктор
  4. Возвращает ссылку на объект

Шаг 2: Использование объекта

user.setEmail("john@example.com");
List<User> users = new ArrayList<>();
users.add(user);
// Объект остаётся в памяти, пока на него есть ссылка

Шаг 3: Потеря ссылки

user = null;  // Ссылка удалена
// или
users.clear();  // Ссылки удалены из списка
// Объект больше не доступен → становится кандидатом на удаление

3. Сборка мусора (Garbage Collection)

Алгоритм Working Set (Mark-Sweep-Compact):

1. MARK (Пометить)
   JVM начинает с корневых ссылок (stack, global variables)
   Помечает все доступные объекты:
   
   User user = new User();  // ✓ Помечен (доступен)
   user = null;             // ✗ Не помечен (недоступен)

2. SWEEP (Подмести)
   JVM удаляет все объекты, которые не помечены
   
   // Вызывается finalize(), если определён
   @Override
   protected void finalize() {
       System.out.println("Объект удаляется");
   }

3. COMPACT (Компактифицировать)
   JVM сдвигает оставшиеся объекты, устраняя "дыры" в памяти

Пример работы GC:

public class GCExample {
    public static void main(String[] args) {
        // Создаём объекты
        User user1 = new User("John");      // Память выделена
        User user2 = new User("Jane");      // Память выделена
        
        user1 = null;  // user1 больше не доступен
        
        // Когда Eden Space заполнится, произойдёт Minor GC
        // user1 будет удалён, user2 останется или переместится
        
        System.gc();  // Просим GC (не гарантировано!)
    }
}

4. Поколениевая сборка мусора

Основная идея: молодые объекты чаще умирают, чем старые

Шаг 1: Minor GC (Молодое поколение)
Частота: очень часто (каждые несколько секунд)
Длительность: 5-100 ms

  Eden  →  Survivor 0  →  Survivor 1  →  Old Generation
   New       (Age=1)      (Age=2)        (Age=15+)

Шаг 2: Major GC (Старое поколение)
Частота: редко (зависит от размера Old Generation)
Длительность: может быть несколько секунд

Шаг 3: Full GC
Очистка всей памяти (Eden + Old)
Длительность: несколько секунд или больше

Процесс Minor GC:

// Начальное состояние
Eden: [obj1, obj2, obj3, obj4] (80% заполнено)
Survivor0: []
Survivor1: []

// Произошла ссылка на obj1
obj1 = null;

// Запустился Minor GC
// 1. Пометили доступные: obj2, obj3, obj4
// 2. Удалили obj1 (помет не поставили)
// 3. Переместили выживших в Survivor0

Eden: [] (очищена, готова для новых объектов)
Survivor0: [obj2(age=1), obj3(age=1), obj4(age=1)]
Survivor1: []

5. Возраст объекта (Object Age)

Каждый раз, когда объект переживает GC, его возраст увеличивается:

public class ObjectAging {
    public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();  // Age = 0
        
        // После Minor GC 1
        // Age = 1 (если пережил)
        
        // После Minor GC 2
        // Age = 2 (если пережил)
        
        // ...
        
        // После Minor GC 15 (по умолчанию MaxTenuringThreshold=15)
        // Объект переходит в Old Generation
        // Age >= 15 → OLD GENERATION
    }
}

6. Алгоритмы сборки мусора

G1GC (Garbage First) — современный стандарт:

// Запуск с G1GC
java -XX:+UseG1GC -Xmx2G MyApplication

// Плюсы:
// - Предсказуемые паузы
// - Хорошо масштабируется на большой памяти
// - Удаляет фрагментацию памяти

ZGC (Z Garbage Collector) — для очень больших heap:

java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xmx10G MyApplication

// Плюсы:
// - Микросекундные паузы
// - Может работать с heap > 8 ТБ

Serial GC — для малых приложений:

java -XX:+UseSerialGC -Xmx512M MyApplication

// Плюсы:
// - Простой и эффективный для одного потока

Parallel GC:

java -XX:+UseParallelGC -Xmx4G MyApplication

// Плюсы:
// - Использует несколько потоков для GC
// - Хорошая пропускная способность

7. Управление памятью в коде

Избегай утечек памяти (Memory Leaks):

// ❌ Утечка памяти - объекты не удаляются
public class MemoryLeak {
    static List<Object> cache = new ArrayList<>();  // Растёт бесконечно
    
    public void addToCache(Object obj) {
        cache.add(obj);
        // Никогда не удаляем из кеша!
    }
}

// ✅ Правильно - контролируем размер кеша
public class ProperCache {
    private static final int MAX_SIZE = 1000;
    private static LinkedHashMap<String, Object> cache = 
        new LinkedHashMap<String, Object>(MAX_SIZE, 0.75f, true) {
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return size() > MAX_SIZE;
            }
        };
}

Weak References для кешей:

// Объект может быть удалён GC, даже если на него есть ссылка
public class WeakReferenceExample {
    WeakHashMap<String, User> cache = new WeakHashMap<>();
    
    public void cacheUser(String key, User user) {
        cache.put(key, user);
        // Если нет других ссылок на user, он может быть удалён при GC
    }
}

8. Мониторинг и отладка GC

Логирование событий GC:

# Включаем логирование GC
java -Xlog:gc*:file=gc.log:time,level,tags MyApplication

# или старый синтаксис
java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log MyApplication

Анализ памяти в коде:

public class MemoryMonitoring {
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();
        
        long maxMemory = runtime.maxMemory();      // -Xmx
        long totalMemory = runtime.totalMemory();  // Выделено JVM
        long freeMemory = runtime.freeMemory();    // Свободная память
        long usedMemory = totalMemory - freeMemory;
        
        System.out.println("Max Memory: " + (maxMemory / 1024 / 1024) + " MB");
        System.out.println("Used Memory: " + (usedMemory / 1024 / 1024) + " MB");
        System.out.println("Free Memory: " + (freeMemory / 1024 / 1024) + " MB");
        
        // Явный запрос GC (не гарантировано)
        System.gc();
    }
}

JVM параметры для оптимизации:

# Размер памяти
java -Xms1G -Xmx4G MyApplication
# -Xms: начальный размер
# -Xmx: максимальный размер

# G1GC с паузой максимум 200мс
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 MyApplication

# Включить логирование
java -XX:+PrintGCDetails -XX:+PrintGCDateStamps MyApplication

9. Finalization (и его проблемы)

Finalize вызывается перед удалением объекта (избегай этого):

public class Resource {
    @Override
    protected void finalize() throws Throwable {
        // Запускается перед удалением, но ОЧЕНЬ медленно
        // Может задержать GC
        cleanup();
        super.finalize();
    }
    
    // ✅ Используй AutoCloseable вместо finalize
}

public class BetterResource implements AutoCloseable {
    @Override
    public void close() throws Exception {
        cleanup();
    }
}

// Использование
try (BetterResource resource = new BetterResource()) {
    // Работа с ресурсом
} // автоматически вызовется close()

10. Практический пример

public class GCPracticalExample {
    public static void main(String[] args) throws InterruptedException {
        // Симуляция нагрузки на память
        List<byte[]> memory = new ArrayList<>();
        
        for (int i = 0; i < 100; i++) {
            // Создаём большой объект (10 MB)
            byte[] data = new byte[10 * 1024 * 1024];
            memory.add(data);
            
            System.out.println("Allocated: " + (i + 1) * 10 + " MB");
            
            // После ~100 MB произойдёт Minor GC
            if (i % 10 == 0) {
                System.gc();  // Просим явно
            }
            
            Thread.sleep(100);
        }
        
        // Когда list выходит из области видимости,
        // все объекты становятся кандидатами на удаление
        memory = null;
        System.gc();
    }
}

Заключение

Очистка памяти в JVM через Garbage Collection работает так:

  1. Объекты создаются в Young Generation (Eden)
  2. Minor GC удаляет недостижимые объекты из Young
  3. Выжившие объекты переходят в Survivor и со временем в Old Generation
  4. Major GC очищает Old Generation (редко и долго)
  5. Full GC очищает всю память (избегай этого!)

Основные принципы:

  • JVM управляет памятью автоматически
  • Молодые объекты удаляются чаще (Minor GC)
  • Старые объекты удаляются редко (Major GC)
  • Используй WeakHashMap для кешей
  • Следи за утечками памяти
  • Выбирай правильный GC алгоритм (G1GC по умолчанию хороший выбор)

Понимание этого механизма помогает писать более эффективный код и избегать проблем с производительностью.