Какие типичные причины утечки памяти (memory leak) в Java?

«Какие типичные причины утечки памяти (memory leak) в Java?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Утечка памяти в Java возникает, когда объекты больше не нужны приложению, но остаются достижимыми для сборщика мусора (GC), не allowing их удалить.

Распространённые причины и примеры:

  1. Статические поля, особенно коллекции: Объекты, добавленные в статическую коллекцию, живут всё время жизни класса.

    public class MemoryLeak {
        private static final List<byte[]> LEAK = new ArrayList<>();
    
        public void causeLeak() {
            LEAK.add(new byte[1024 * 1024]); // 1MB добавляется и никогда не удаляется
        }
    }
  2. Незакрытые ресурсы (Closeable): Потоки, соединения с БД, сокеты.

    try {
        FileInputStream fis = new FileInputStream("largefile.zip");
        // Чтение данных...
        // fis.close(); // УТЕЧКА: поток и связанный с ним native-буфер не освобождаются
    } catch (IOException e) { ... }
    // Всегда используйте try-with-resources!
  3. Неправильные реализации equals() и hashCode(): Объекты, помещённые в HashSet или как ключи в HashMap, могут стать "потерянными", если их хэш-код изменяется, делая их недостижимыми для стандартного поиска, но они остаются в коллекции.

  4. Слушатели событий (Listeners) и callback-и: Если не отписаться, внешний объект удерживается в памяти.

Инструменты для диагностики: VisualVM, Eclipse Memory Analyzer (MAT), Java Flight Recorder (JFR). Они помогают анализировать heap dump и находить цепочки ссылок, удерживающие объекты.