← Назад к вопросам
Как найти утечку памяти в Java
2.0 Middle🔥 191 комментариев
#JVM и управление памятью#Тестирование
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как найти утечку памяти в Java
Утечка памяти в Java — это когда объекты больше не используются, но на них остаются ссылки, препятствующие их удалению сборщиком мусора. Это может привести к OutOfMemoryError. Расскажу о методах диагностики.
Шаг 1: Определить, что есть утечка
Применить мониторинг памяти:
Runtime runtime = Runtime.getRuntime();
// Общая память, выделенная для JVM
long totalMemory = runtime.totalMemory(); // в байтах
// Свободная память
long freeMemory = runtime.freeMemory();
// Используемая память
long usedMemory = totalMemory - freeMemory;
System.out.printf("Total: %d MB, Free: %d MB, Used: %d MB%n",
totalMemory / 1024 / 1024,
freeMemory / 1024 / 1024,
usedMemory / 1024 / 1024);
Повторяйте это периодически. Если используемая память растёт и никогда не падает — есть утечка.
Шаг 2: Инструмент JVisualVM
В JDK входит jvisualvm — графический инструмент мониторинга:
jvisualvm
Он показывает:
- График использования памяти
- Heap dump
- GC активность
- Потоки
- CPU использование
Как использовать:
- Запустите jvisualvm
- Выберите ваше приложение из списка
- Перейдите на вкладку Monitor
- Следите за графиком памяти
Шаг 3: Собрать Heap Dump
Способ 1: Через jmap (командная строка)
# Найти PID приложения
jps
# 12345 MyApp
# 54321 SomeOtherApp
# Создать heap dump
jmap -dump:live,format=b,file=heap.bin 12345
Способ 2: Через jvisualvm
- Правый клик на процесс
- "Heap Dump"
- Файл сохранится автоматически
Способ 3: В коде (программно)
import com.sun.management.HotSpotDiagnosticsMXBean;
import java.lang.management.ManagementFactory;
public void dumpHeap(String filename) {
try {
HotSpotDiagnosticsMXBean bean = ManagementFactory.getPlatformMXBean(
HotSpotDiagnosticsMXBean.class
);
bean.dumpHeap(filename, true);
System.out.println("Heap dumped to " + filename);
} catch (Exception e) {
e.printStackTrace();
}
}
Шаг 4: Анализировать Heap Dump
Способ 1: jhat (Java Heap Analysis Tool)
jhat heap.bin
# Откроет веб-интерфейс на localhost:7000
Способ 2: Eclipse Memory Analyzer (MAT)
Это мощный инструмент анализа:
# Скачать с eclipse.org
# Открыть heap.bin в MAT
Mat покажет:
- Какие объекты занимают больше всего памяти
- Какие ссылки удерживают объекты
- "Suspect" объекты, вероятно являющиеся причиной утечки
Шаг 5: Анализировать в коде
Пример утечки памяти:
public class MemoryLeakExample {
// Статический список, никогда не очищается
private static List<byte[]> memoryCache = new ArrayList<>();
public void addToCache(byte[] data) {
memoryCache.add(data); // Объекты остаются в памяти навсегда!
}
}
Как найти в heap dump:
- Ищите List/HashMap/Queue, содержащие много объектов
- Проверьте статические поля
- Ищите циклические ссылки
- Ищите слушатели (listeners), которые не отписались
Шаг 6: Типичные причины утечек
1. Статические коллекции
// ПЛОХО: объекты в памяти навсегда
private static List<MyObject> cache = new ArrayList<>();
// ХОРОШО: с ограничением размера
private static List<MyObject> cache = new LinkedList<MyObject>() {
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > 1000; // Максимум 1000 элементов
}
};
// ИЛИ: используй WeakReference
private static Map<String, WeakReference<MyObject>> cache = new HashMap<>();
2. Неотписанные слушатели
// ПЛОХО
public void subscribe() {
button.addClickListener(this::handleClick);
// Никогда не отписываемся!
}
// ХОРОШО
public void subscribe() {
button.addClickListener(this::handleClick);
}
public void cleanup() {
button.removeClickListener(this::handleClick);
}
3. Незакрытые ресурсы
// ПЛОХО
public void readFile() throws IOException {
FileInputStream fis = new FileInputStream("data.txt");
// ... читаем ...
// Забыли закрыть!
}
// ХОРОШО: try-with-resources
public void readFile() throws IOException {
try (FileInputStream fis = new FileInputStream("data.txt")) {
// ... читаем ...
// Автоматически закроется
}
}
4. Циклические ссылки
public class Parent {
private Child child = new Child(this); // child ссылается на parent
}
public class Child {
private Parent parent; // parent ссылается на child
public Child(Parent parent) {
this.parent = parent;
}
}
// Удаление parent не удаляет child, и наоборот (в некоторых сценариях)
5. ThreadLocal утечки
// ПЛОХО: если не очистить
public class ThreadLocalLeak {
private static ThreadLocal<Connection> connectionHolder =
ThreadLocal.withInitial(() -> new Connection());
// Если поток переиспользуется (thread pool),
// Connection останется в памяти
}
// ХОРОШО: всегда очищайте
public void cleanup() {
connectionHolder.remove();
}
Шаг 7: Инструменты мониторинга
JMX (Java Management Extensions)
import java.lang.management.*;
public class MemoryMonitor {
public static void monitorMemory() {
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
System.out.println("Heap:");
System.out.println("Init: " + heapUsage.getInit() / 1024 / 1024 + " MB");
System.out.println("Used: " + heapUsage.getUsed() / 1024 / 1024 + " MB");
System.out.println("Max: " + heapUsage.getMax() / 1024 / 1024 + " MB");
System.out.println("Committed: " + heapUsage.getCommitted() / 1024 / 1024 + " MB");
}
}
Micrometer + Prometheus
// Для production мониторинга
@Configuration
public class MetricsConfig {
@Bean
public MeterRegistry meterRegistry() {
PrometheusMeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
// Память
Gauge.builder("jvm.memory.used",
() -> Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
.baseUnit("bytes")
.register(registry);
return registry;
}
}
Пошаговый процесс диагностики
1. Заметить проблему
↓
2. Мониторить память (Runtime API или jvisualvm)
↓
3. Подтвердить утечку (график памяти растёт)
↓
4. Создать heap dump (jmap или jvisualvm)
↓
5. Анализировать dump (MAT или jhat)
↓
6. Найти объект, занимающий память
↓
7. Найти ссылку на объект (Path to Root)
↓
8. Найти код, создающий эту ссылку
↓
9. Исправить код (удалить ссылку, очистить коллекцию и т.д.)
↓
10. Повторить тестирование
Флаги JVM для диагностики
java -Xmx512m \
-XX:+PrintGCDetails \
-XX:+PrintGCTimeStamps \
-Xloggc:gc.log \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=./heapdump.hprof \
MyApplication
Где:
-Xmx512m— максимум памяти 512 MB-XX:+PrintGCDetails— выводить детали сборки мусора-XX:+HeapDumpOnOutOfMemoryError— создать dump при ошибке
Лучшие практики
- Используй try-with-resources для всех ресурсов
- Не кешируй без необходимости, или используй WeakReference
- Отписывайся от слушателей в cleanup методах
- Избегай статических коллекций, если не нужна глобальная кэш
- Мониторь память в production через JMX или Micrometer
- Регулярно проверяй GC логи на странные паттерны