Какие типичные причины утечки памяти (memory leak) в 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 и находить цепочки ссылок, удерживающие объекты.

Ответ 18+ 🔞

А, утечка памяти в Java, классика жанра, ёпта! Это когда твоё приложение жрёт оперативку как последний алкаш, а потом падает с OutOfMemoryError, и ты сидишь такой: «Нахуя? Я же всё чистил!». А оказывается, не чистил, блядь. Объекты-то вроде и не нужны, но до них ещё можно дотянуться, и сборщик мусора их не трогает — сидят, суки, в памяти, как в курортном санатории.

Вот тебе самые частые грабли, на которые все наступают:

  1. Статические коллекции — это пиздец, а не хранилище. Засунул туда что-то — и прощай, навсегда. Пока класс в памяти, твои объекты будут там висеть, как проклятые.

    public class MemoryLeak {
        private static final List<byte[]> LEAK = new ArrayList<>();
    
        public void causeLeak() {
            LEAK.add(new byte[1024 * 1024]); // Ёбнул мегабайт в статику — и он там навечно поселился, как чёрт в болоте.
        }
    }
  2. Незакрытые ресурсы — это вообще отдельная песня, ядрёна вошь! Открыл поток, файл, соединение — и забыл. А они там в нативном мире буферы держат, память жрут. И приложение твоё потихоньку отекает, как труп в речке.

    try {
        FileInputStream fis = new FileInputStream("largefile.zip");
        // Почитал чего-то...
        // fis.close(); // А ЗАКРЫТЬ ЗАБЫЛ, ПИДОРАС! Теперь этот поток тебе всю память выжрет.
    } catch (IOException e) { ... }
    // Запомни, как «Отче наш»: try-with-resources, и никаких гвоздей!
  3. Кривые equals() с hashCode() — это просто высший пилотаж ебли. Засунул объект в HashMap как ключ, потом его поля поменял — хэш изменился. И всё, привет. Найти его уже не можешь, а он там сидит, мусорный призрак, и память занимает. Красота, блядь!

  4. Слушатели (Listeners) и callback-и — тихие убийцы. Подписался на событие и не отписался. А тот, кто держит ссылку на твой callback, не даёт ему умереть. И пошло-поехало: утечка ебаная, а ты и не знаешь, откуда ноги растут.

Чем ловить эту нечисть? Инструменты, блядь, есть! VisualVM — как рентген, посмотреть, что жрёт. Eclipse Memory Analyzer (MAT) — это уже вскрытие трупа (heap dump), покажет, кто на ком сидит и не даёт собраться мусору. Java Flight Recorder (JFR) — как чёрный ящик, всё записывает. Включаешь, ловишь момент, когда память утекает, а потом разбираешь, кто виноват. Главное — не паниковать, а методично искать, какая хитрая жопа удерживает лишнее.