Как увидеть из Heap Dumps ошибку в работе Garbage Collection
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как увидеть из Heap Dumps ошибку в работе Garbage Collection
Что такое Heap Dump
Heap Dump — это снимок состояния Java heap в момент времени, содержащий информацию об всех объектах в памяти и их связях. Это мощный инструмент для диагностики проблем с GC и утечками памяти.
Создание Heap Dump
Способ 1: Через jmap (при дефекте памяти)
# Найти PID процесса Java
jps -l
# Создать dump при наличии процесса
jmap -dump:live,format=b,file=heap.bin <PID>
# Пример
jmap -dump:live,format=b,file=/tmp/heap_$(date +%s).bin 12345
Способ 2: Автоматически при OutOfMemoryError
java -XX:+HeapDumpOnOutOfMemoryError \n -XX:HeapDumpPath=/var/dumps/heapdump.bin \n -jar myapp.jar
Способ 3: Через JVisualVM (GUI инструмент)
Запустить: jvisualvm
1. Подключиться к приложению
2. Memory вкладка
3. Нажать "Heap Dump"
4. Сохранить файл
Способ 4: Через jcmd
jcmd <PID> GC.heap_dump /tmp/heapdump.bin
Анализ Heap Dump
Инструменты анализа:
- Eclipse MAT (Memory Analyzer Tool) — мощный профессиональный инструмент
- JProfiler — GUI анализ
- YourKit — интерактивный анализ
- Heap Hero — облачный анализ
- JDK jhat — встроенный инструмент
Признаки проблем с GC в Heap Dump
1. Утечка памяти (Memory Leak)
// Проблемный код
public class MemoryLeakExample {
private static List<byte[]> cache = new ArrayList<>();
public void addToCache(byte[] data) {
cache.add(data); // Объекты никогда не удаляются!
}
public void clearCache() {
cache.clear();
}
}
В Heap Dump видишь:
- Огромное количество одинаковых объектов
- Растущий размер одной коллекции
- Пути до них идут от static полей
Найти утечку в Eclipse MAT:
1. Открыть heap dump
2. Leak Suspects → Report
3. Увидишь объекты, держащие большую часть памяти
4. Trace Back to Garbage Collection Root
5. Найдёшь цепочку ссылок до статических полей
2. Чрезмерное создание объектов (Object Proliferation)
// Неэффективный код
public String processString(String input) {
String result = input;
for (int i = 0; i < 1000; i++) {
result = result + "_" + i; // Создаёт новый String на каждой итерации!
}
return result;
}
// Правильный код
public String processString(String input) {
StringBuilder result = new StringBuilder(input);
for (int i = 0; i < 1000; i++) {
result.append("_").append(i);
}
return result.toString();
}
В Heap Dump видишь:
- Множество экземпляров одного класса (например, String)
- Размер объектов небольшой, но их очень много
- В список попадает java.lang.String на 50-70% памяти
3. Неправильная работа GC (Heap Fragmentation)
Видишь в Heap Dump:
- Много объектов возраста 0 (молодое поколение переполнено)
- Old generation заполнено на 90%+
- Множество больших объектов, которые не удаляются
Решение:
# Увеличить размер heap
java -Xmx4g -jar myapp.jar
# Оптимизировать GC
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar myapp.jar
Практический анализ в Eclipse MAT
Шаг 1: Открыть Heap Dump
File → Open Heap Dump → выбрать heap.bin
Шаг 2: Посмотреть Dominator Tree
Window → Heap Dump Analysis → Dominator Tree
Это показывает объекты, занимающие больше всего памяти
и какие ссылки их держат в памяти
Шаг 3: Анализировать путь до GC Root
// Если видишь в Dominator Tree большой List<User>
User[] → List → HashMap → CacheManager (static) → Class
// Это значит:
public class CacheManager {
private static HashMap<String, List<User>> cache = new HashMap<>();
// static поле держит ссылку, объекты никогда не удаляются
}
Шаг 4: OQL (Object Query Language) запросы
-- Найти все String длиной > 100 символов
SELECT * FROM java.lang.String s WHERE s.value.length > 100
-- Найти все HashMap, которые содержат > 1000 элементов
SELECT * FROM java.util.HashMap h WHERE h.table.length > 1000
-- Найти все объекты User
SELECT * FROM com.example.User
Детектирование проблем через GC логи
Включить GC logging:
java -XX:+PrintGCDetails \n -XX:+PrintGCDateStamps \n -Xloggc:/var/logs/gc.log \n -jar myapp.jar
Анализировать логи:
2024-01-15T10:23:45.123+0000: 12.345: [GC (Allocation Failure)
[PSYoungGen: 8388000K->1000K(9216000K)]
8388000K->1388000K(15728640K), 0.0234567 secs]
Что это означает:
- Young Generation: 8GB → 1MB (хорошо, объекты собрали)
- Полная heap: 8GB → 1.3GB
- Пауза GC: 23 миллисекунды (приемлемо)
Признаки проблем в GC логах:
1. Частые Full GC
[Full GC (Ergonomics) ... 0.5234 secs] // > 0.5 сек = плохо
2. Растущий размер heap после GC
[PSYoungGen: 9000K->8000K] // Почти ничего не собрали
3. GC паузы растут со временем
0.023 → 0.045 → 0.089 сек // Trend к замедлению
Правильная интерпретация
Утечка памяти:
- После GC размер heap не уменьшается
- Одно и то же выделение памяти происходит каждый цикл
- В Dominator Tree видны огромные коллекции, которые никогда не чистятся
Неправильный размер Heap:
- Full GC происходит слишком часто
- Heap постоянно > 85% заполнен
- Нужно увеличить -Xmx
Неправильный выбор GC:
- При 32+ GB памяти используется ParallelGC (нужен G1GC)
- Высокие паузы GC (> 100ms) — нужен GC с низкой паузой
Лучшие практики
- Регулярно снимай Heap Dumps перед выпуском
- Анализируй GC логи в production
- Мониторь память через Micrometer
- Используй правильные параметры JVM для твоего случая
- Тестируй под реальными нагрузками
@Component
public class MemoryMonitor {
private final MeterRegistry registry;
@Scheduled(fixedRate = 10000)
public void monitorMemory() {
MemoryMXBean memBean = ManagementFactory.getMemoryMXBean();
long used = memBean.getHeapMemoryUsage().getUsed();
long max = memBean.getHeapMemoryUsage().getMax();
double usage = (double) used / max * 100;
registry.gauge("jvm.heap.usage.percent", usage);
if (usage > 85) {
log.warn("Heap usage is {}%, consider increasing -Xmx", usage);
}
}
}