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

Как происходит сборка мусора в JVM

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

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

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

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

Как происходит сборка мусора в JVM

Garbage Collection (GC) — это автоматический механизм управления памятью в JVM, который выявляет и освобождает память, занимаемую объектами, которые больше не используются программой. GC позволяет разработчикам не беспокоиться об явном освобождении памяти (как в C/C++), но требует понимания принципов работы для оптимизации производительности.

Структура памяти JVM (Heap)

Heap подразделяется на регионы:

public class HeapStructureExample {
    public static void main(String[] args) {
        // Heap в Java подразделяется на:
        // 1. Young Generation (молодое поколение)
        //    - Eden Space (80% молодого поколения)
        //    - Survivor Space 0 (10% молодого поколения)
        //    - Survivor Space 1 (10% молодого поколения)
        // 2. Old Generation (старое поколение)
        // 3. Permanent Generation / Metaspace (Java 8+)
        
        // Команда для просмотра структуры heap:
        // java -Xms1g -Xmx2g -XX:NewRatio=2 MyApp
        //   -Xms1g = начальный размер heap
        //   -Xmx2g = максимальный размер heap
        //   -XX:NewRatio=2 = отношение Young:Old = 1:2
    }
}

Generational Hypothesis

Основной принцип GC - объекты часто умирают молодыми:

public class GenerationalHypothesis {
    public static void main(String[] args) {
        // Молодое поколение собирается часто, старое - редко
        
        // Young Generation objects (часто удаляются)
        for (int i = 0; i < 1_000_000; i++) {
            String temp = new String("temporary");
            // temp выходит из области видимости -> становится мусором
        } // Minor GC происходит часто
        
        // Old Generation objects (редко удаляются)
        List<String> persistent = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            persistent.add(new String("persistent-" + i));
        }
        // persistent может жить долго -> Major GC редко трогает
    }
}

Процесс сборки мусора

1. Minor GC (Молодое поколение)

Происходит часто (каждые несколько секунд).

Public class MinorGCExample {
    public static void main(String[] args) throws InterruptedException {
        // Фаза 1: Marking (разметка)
        // GC определяет, какие объекты живы
        List<String> alive = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            String temp = new String("garbage"); // Мусор
            alive.add(new String("keep"));       // Живой объект
        }
        // GC найдёт живые объекты
        
        // Фаза 2: Sweeping (очистка)
        // GC удаляет мёртвые объекты из Eden
        
        // Фаза 3: Copying (копирование)
        // Живые объекты копируются в Survivor Space
        
        System.out.println("Minor GC завершена");
    }
}

2. Major GC / Full GC (Полное поколение)

Происходит редко, может быть очень дорого.

public class MajorGCExample {
    public static void main(String[] args) throws OutOfMemoryError {
        List<Object> leakingObjects = new ArrayList<>();
        
        try {
            // Заполняем Old Generation
            while (true) {
                for (int i = 0; i < 1000; i++) {
                    byte[] largeArray = new byte[1024 * 100]; // 100KB
                    leakingObjects.add(largeArray);
                    // Объекты переживают Minor GC -> идут в Old
                }
                
                // Когда Old Generation почти полна
                // -> Major GC (может занять секунды)
                // -> Если памяти всё равно мало -> OutOfMemoryError
            }
        } catch (OutOfMemoryError e) {
            System.out.println("Heap переполнена!");
        }
    }
}

Алгоритмы GC

1. Serial GC (Серийный)

Самый простой, использует один поток.

# Включение Serial GC
java -XX:+UseSerialGC MyApp

# Процесс:
# 1. Pause application (остановка приложения)
# 2. Mark-Sweep-Compact (маркировка, удаление, уплотнение)
# 3. Resume application

# Проблема: Stop-the-world pause может быть большим
public class SerialGCExample {
    public static void main(String[] args) {
        // Serial GC хорошо для small heap и single-threaded приложений
        List<String> strings = new ArrayList<>();
        
        for (int i = 0; i < 10_000; i++) {
            strings.add(new String("Object " + i));
            // Периодически происходит pause (неприемлемо для latency-sensitive)
        }
    }
}

2. Parallel GC (Параллельный)

Использует несколько потоков для GC.

# Включение Parallel GC (по умолчанию в Java 8)
java -XX:+UseParallelGC -XX:ParallelGCThreads=8 MyApp

# Процесс:
# 1. Pause application
# 2. Multiple threads выполняют marking и sweeping
# 3. Resume application

# Преимущество: Faster GC time
# Недостаток: Still has stop-the-world pause

3. CMS GC (Concurrent Mark-Sweep)

Выполняет маркировку параллельно с приложением.

# Включение CMS GC
java -XX:+UseConcMarkSweepGC MyApp

# Процесс:
# 1. Initial Mark (pause short) - отмечает root objects
# 2. Concurrent Mark (parallel) - маркирует живые объекты
# 3. Remark (pause short) - уточняет маркировку
# 4. Concurrent Sweep (parallel) - удаляет мусор

# Преимущество: Низкая latency
# Недостаток: Fragmentation, CPU использование
public class CMSGCBenefit {
    public static void main(String[] args) throws InterruptedException {
        // CMS позволяет приложению работать во время GC
        
        Thread gcThread = new Thread(() -> {
            System.out.println("CMS GC: Concurrent Mark-Sweep");
            System.out.println("Приложение МОЖЕТ работать во время маркировки");
        });
        
        // Главная программа
        for (int i = 0; i < 1000; i++) {
            List<String> data = new ArrayList<>();
            for (int j = 0; j < 100; j++) {
                data.add(new String("data"));
            }
            Thread.sleep(10); // Приложение работает
        }
    }
}

4. G1GC (Garbage First)

Современный алгоритм для больших heap, по умолчанию в Java 10+.

# Включение G1GC
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 MyApp

# Процесс:
# 1. Heap разделена на множество small regions (обычно 2048)
# 2. GC приоритизирует regions с наибольшим мусором
# 3. Инкрементальные паузы вместо полных паузы

# Преимущество: Predictable pause times
# Рекомендуется для: Large heaps (> 4GB), latency-sensitive приложения
public class G1GCExample {
    public static void main(String[] args) {
        // G1GC оптимален для большие heap
        List<byte[]> allocation = new ArrayList<>();
        
        for (int i = 0; i < 1_000_000; i++) {
            byte[] chunk = new byte[8192]; // 8KB chunks
            allocation.add(chunk);
            
            if (i % 10000 == 0) {
                // G1 инкрементально собирает мусор
                // Паузы предсказуемы (~200ms с настройкой выше)
            }
        }
    }
}

Mark-Sweep-Compact алгоритм

Фазы работы GC:

public class MarkSweepCompactExample {
    
    static class GCPhases {
        // Фаза 1: MARK (маркировка)
        // GC отмечает все живые объекты, начиная с GC roots
        public void phase1Mark() {
            // GC roots включают:
            // - Local variables в stack frames
            // - Static variables в классах
            // - Objects referenced by threads
            
            // DFS/BFS сканирует граф объектов
            // Живые объекты отмечаются
        }
        
        // Фаза 2: SWEEP (удаление мусора)
        public void phase2Sweep() {
            // GC проходит по heap
            // Неотмеченные объекты удаляются
            // Фрагментация может увеличиться
        }
        
        // Фаза 3: COMPACT (уплотнение)
        public void phase3Compact() {
            // Живые объекты компактируются
            // Фрагментация устраняется
            // Может быть дорого
        }
    }
    
    public static void main(String[] args) {
        // Пример из исходного heap
        // Heap: [obj1][free][obj2][obj3][free][free][obj4]
        // 
        // После Mark: obj1, obj2, obj3, obj4 отмечены
        // 
        // После Sweep: [obj1][obj2][obj3][obj4][free][free][free]
        // 
        // После Compact: Живые объекты близко друг к другу
    }
}

Отслеживание GC

Логирование GC:

# Включение GC logs
java -Xms1g -Xmx4g \
     -Xlog:gc*:file=gc.log:time,level,tags \
     -XX:+PrintGCDetails \
     -XX:+PrintGCDateStamps \
     MyApp

Анализ в Java коде:

public class GCMonitoringExample {
    public static void main(String[] args) {
        MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
        List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
        
        for (GarbageCollectorMXBean gc : gcBeans) {
            System.out.println("GC Name: " + gc.getName());
            System.out.println("Collection Count: " + gc.getCollectionCount());
            System.out.println("Collection Time: " + gc.getCollectionTime() + "ms");
        }
        
        // Мониторинг heap
        MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
        System.out.println("Heap Usage: " + heapUsage.getUsed() / (1024 * 1024) + "MB");
    }
}

Оптимизация GC

1. Настройка размеров heap:

# -Xms: начальный размер
# -Xmx: максимальный размер
# Обычно: -Xms = -Xmx для предсказуемости

java -Xms4g -Xmx4g MyApp

2. Выбор подходящего GC:

# Latency-sensitive приложения (Real-time)
java -XX:+UseG1GC -XX:MaxGCPauseMillis=100 MyApp

# Throughput-focused приложения (Batch processing)
java -XX:+UseParallelGC -XX:ParallelGCThreads=16 MyApp

# Старые приложения (Java 8)
java -XX:+UseConcMarkSweepGC MyApp

3. Избежание утечек памяти:

public class MemoryLeakExample {
    // ПЛОХО: Static collection без очистки
    private static List<String> cache = new ArrayList<>();
    
    public void addToCache(String key, String value) {
        cache.add(key + ":" + value);
        // cache растёт бесконечно -> OutOfMemoryError
    }
    
    // ХОРОШО: Ограниченный cache с eviction
    private final Map<String, String> limitedCache = 
        new LinkedHashMap<String, String>(16, 0.75f, true) {
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return size() > 100; // Максимум 100 записей
            }
        };
}

4. Профилирование heap:

# Использование JVisualVM
jvisualvm &

# Или командная строка
jps -l                    # Список JVM процессов
jmap -histo <pid>        # Heap histogram
jmap -dump:live,format=b,file=heap.bin <pid>  # Dump heap
jhat heap.bin             # Анализ dump

Заключение

Сборка мусора в JVM — это сложный процесс, включающий отслеживание живых объектов и освобождение памяти. Выбор правильного алгоритма GC критичен для производительности приложения. G1GC является рекомендуемым выбором для современных приложений, обеспечивая баланс между latency и throughput. Понимание принципов GC помогает разработчикам писать более эффективный код и избегать проблем с памятью.