В Java управление памятью осуществляется за счёт автоматической сборки мусора (Garbage Collector, GC). JVM самостоятельно отслеживает объекты в памяти: если на объект больше нет ссылок, он становится недостижимым и может быть удалён.
GC состоит из разных фаз, таких как Mark, Sweep и иногда Compact. В фазе Mark объекты, до которых можно дотянуться из корней (GC roots), помечаются как живые. После этого начинается Sweep: неиспользуемые объекты удаляются. Иногда выполняется Compact — дефрагментация памяти.
Есть разные типы GC: Serial, Parallel, CMS, G1. Их стоит выбирать в зависимости от типа нагрузки на приложение.
List<byte[]> dataList = new ArrayList<>(); while(true) { dataList.add(new byte[1024*1024]); // Объекты остаются достижимыми }
В этом примере объекты никогда не станут недостижимыми, GC их не удалит, и произойдёт OutOfMemoryError.
Если у объекта больше нет ссылок, когда гарантируется вызов его финализатора (finalize())?
Ответ: Вызов метода finalize() не гарантируется вовсе! Он может быть вызван ни разу или с задержкой. Нельзя полагаться на finalize() для освобождения ресурсов.
@Override protected void finalize() throws Throwable { // Может не отработать! }
История
В крупной e-commerce системе разработчики надеялись автоматизировать очистку временных файлов с помощью метода finalize(), но из-за редкого вызова этого метода кэш переполнился временными файлами, что привело к нехватке диска и простоям.
История
В high-load REST API случайно был сохранён внутренний кеш ссылок на большие объекты, что препятствовало освобождению памяти. После падения приложения с OutOfMemoryError анализ показал ошибку в методе кэширования.
История
Финализаторы использовались для освобождения сетевых соединений, считая это "надежным способом". Это приводило к тому, что соединения висели в системе неопределённо долго, вызывая блокировки и истощение пула соединений.