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

Когда объекты перемещаются между отсеками Heap в JVM?

2.4 Senior🔥 131 комментариев
#JVM и управление памятью

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

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

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

Перемещение объектов между отсеками Heap

Структура Heap в JVM

Heap разделён на несколько отсеков (generations):

HEAP
┌─────────────────────────────────┐
│  OLD GENERATION (Tenured)       │  долгоживущие объекты
├─────────────────────────────────┤
│  SURVIVOR (S0/S1)               │  пережившие объекты
├─────────────────────────────────┤
│  YOUNG GENERATION (Eden)        │  новые объекты
└─────────────────────────────────┘

1. Young Generation (Молодое поколение)

Все новые объекты создаются в Eden Space:

User user = new User("Alice");  // Создаётся в Eden

Eden характеристики:

  • Большая часть Young Generation
  • Чистится часто (Minor GC)
  • Быстрая очистка

2. Survivor Spaces (S0 и S1)

Объекты, пережившие Minor GC, перемещаются в Survivor Space:

Minor GC процесс:

1. Eden переполнена →
2. Minor GC запускается →
3. Живые объекты перемещаются в S0 →
4. Мёртвые объекты удаляются →
5. Eden очищается

Визуально:

До Minor GC:
Eden  [объект1] [объект2] [объект3]
S0    [пусто]
S1    [пусто]

После Minor GC:
Eden  [пусто]
S0    [объект1] [объект3]  <- пережившие
S1    [пусто]

3. Tenuring (Перемещение в Old Generation)

Объекты переходят в Old Generation после несколько Minor GC.

Каждый объект имеет счётчик возраста (age):

public class HeapMovement {
    public static void main(String[] args) {
        // Объект создан в Eden
        byte[] data = new byte[1024];
        // age = 0
        
        // После 1-го Minor GC: age = 1
        // После 2-го Minor GC: age = 2
        // После 3-го Minor GC: age = 3
        // ...
        // После N-го Minor GC (tenuring threshold): age >= 15
        // → переходит в Old Generation
    }
}

Tenuring Threshold (порог возраста):

По умолчанию = 15 (для G1: может быть динамический)

# Установить свой порог
java -XX:InitialTenuringThreshold=5 -XX:MaxTenuringThreshold=10 MyApp

Пример жизненного цикла объекта

public class ObjectLifecycle {
    static List<byte[]> objects = new ArrayList<>();
    
    public static void main(String[] args) {
        // Объект 1: долгоживущий
        byte[] persistent = new byte[1024];
        objects.add(persistent);
        // persistent переживёт несколько Minor GC
        // → в Old Generation
        
        // Объект 2: временный
        for (int i = 0; i < 1000; i++) {
            byte[] temp = new byte[1024];
            // temp удалится в первом же Minor GC
            // → никогда не будет в Old Gen
        }
    }
}

4. Major GC (Full GC)

Отсек Old Generation очищается отдельно, реже:

Minor GC   - часто, быстро (Eden, S0, S1)
Major GC   - редко, медленно (Old Generation)
Full GC    - редко, очень медленно (весь Heap)

Когда происходит Major GC:

  • Old Generation переполнена
  • Явный вызов System.gc() (не рекомендуется)
  • CMS GC рассчитал, что нужна cleanup
// Провоцируем Major GC
public void triggerFullGC() {
    byte[] huge = new byte[100 * 1024 * 1024];  // 100MB
    // Если недостаточно памяти → Full GC
}

Визуализация жизненного цикла

Объект создан (age=0)
        ↓
   [Eden Space]
        ↓
  Minor GC #1 (age=1)
        ↓
   [Survivor S0]
        ↓
  Minor GC #2 (age=2)
        ↓
   [Survivor S1]  ← копируется туда
        ↓
  Minor GC #3 (age=3)
        ↓
   [Survivor S0]  ← копируется обратно
        ↓
   ... (age повышается)
        ↓
  Minor GC #15 (age=15, >= threshold)
        ↓
 [Old Generation]  ← PROMOTION!
        ↓
  Major GC (редко)
        ↓
   УДАЛЕН

Настройка поведения

Для G1 GC (современный):

java -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:InitialHeapSize=1g \
     -XX:MaxHeapSize=4g \
     MyApp

Для Parallel GC:

java -XX:+UseParallelGC \
     -XX:NewRatio=3 \
     -XX:SurvivorRatio=8 \
     MyApp

Мониторинг перемещений

Посмотреть age объектов:

java -XX:+PrintTenuringDistribution \
     -XX:+PrintGCDetails \
     -XX:+PrintGCTimeStamps \
     MyApp

Вывод:

Desired survivor size 52428800 bytes, new threshold 6 (max 15)
Age table with threshold 6 (max 15):
age  1:    100000 bytes,    100000 total
age  2:     50000 bytes,    150000 total
age  3:     25000 bytes,    175000 total
age  4:     12000 bytes,    187000 total
age  5:      6000 bytes,    193000 total
age  6:      3000 bytes,    196000 total
    196000 → переместятся в Old Gen

Практические примеры

Долгоживущий объект (переходит в Old Gen):

public class Cache {
    private static Map<String, Data> cache = new HashMap<>();
    
    public void cacheData(String key, Data value) {
        cache.put(key, value);  // Останется в памяти долго
        // После нескольких Minor GC → Old Gen
    }
}

Временные объекты (удаляются быстро):

public void processStream(Stream<String> stream) {
    stream
        .map(String::toLowerCase)      // Временный объект
        .filter(s -> s.length() > 5)   // Временный объект
        .forEach(System.out::println);
    // Все временные объекты удалены в Eden
    // Никогда не переходят в Old Gen
}

Заключение

Движение объектов в Heap:

  1. Создание → Eden Space
  2. Первый Minor GC → Survivor S0 (age=1)
  3. Второй Minor GC → Survivor S1 (age=2)
  4. После 15 Minor GC → Old Generation (promotion)
  5. Major GC → удаление из Old Generation

Это поколение-ориентированный сборщик мусора. Молодые объекты чистятся часто, старые редко. Это оптимально для большинства приложений!