Сервис освобождает нативный ресурс через PhantomReference, но ссылка в ReferenceQueue появляется не сразу после потери обычной ссылки. Что JVM гарантирует в этом случае?
PhantomReference не гарантирует немедленную постановку ссылки в ReferenceQueue. JVM может добавить её в очередь только после того, как сборщик мусора обнаружит, что объект больше не достижим обычным способом и находится в состоянии phantom-reachable; до запуска и завершения соответствующей обработки GC это может не произойти.
При этом get() у phantom-ссылки всегда возвращает null: она предназначена для уведомления о возможности очистки, а не для доступа к объекту или его воскрешения.
Автоматическая сборка мусора хорошо управляет Java-объектами, но не освобождает внешние ресурсы: файловые дескрипторы, нативную память, соединения с библиотеками C и другие ресурсы вне heap. Механизм финализации оказался плохо предсказуемым: момент вызова неизвестен, обработка усложняет работу GC, а объект потенциально может быть воскрешён.
PhantomReference предоставляет более контролируемый сигнал: объект уже нельзя получить через ссылку и нельзя воскресить, но приложение может узнать, что пора освободить связанный внешний ресурс.
Потеря последней сильной ссылки не означает, что запись немедленно попадёт в очередь. Сборщик мусора может ещё не запускаться, обработка ссылок может выполняться позже, а отдельный поток, читающий очередь, может быть занят.
Если приложение считает постановку в очередь синхронным событием, оно рискует временно удерживать большое количество нативных ресурсов. Обратная ошибка опаснее: нельзя освобождать ресурс просто при исчезновении обычной ссылки, потому что JVM ещё может выполнять метод, использующий объект.
Для phantom-ссылки объект должен быть зарегистрирован в ReferenceQueue, а сама ссылка — удерживаться приложением до момента обработки. Обычно создают таблицу из phantom-ссылки в дескриптор внешнего ресурса. После потери обычной ссылки GC обнаруживает phantom-достижимость объекта и планирует ссылку к постановке в очередь.
Важны несколько ограничений. PhantomReference.get() всегда возвращает null, поэтому данные для очистки нужно хранить отдельно; нельзя хранить там сильную ссылку на сам объект, иначе он останется достижимым. Кроме того, JVM не обещает, что очередь будет обработана до завершения процесса, поэтому критические ресурсы нельзя полагаться освобождать только таким способом.
Для операций, где объект может быть признан недостижимым во время выполнения нативного вызова, применяют Reference.reachabilityFence. Она удерживает объект достижимым до указанной точки и предотвращает преждевременную очистку связанного ресурса. Для обычного управления ресурсами предпочтительнее явный метод закрытия, например AutoCloseable, а phantom-механизм использовать как защитный резерв.
Сервис обработки изображений создавал нативные буферы. Команда добавила PhantomReference и отдельный поток очистки, но RSS процесса рос: очередь заполнялась медленно, а нативные буферы жили дольше ожидаемого.
Рассматривались два варианта. Увеличение частоты GC могло ускорить обнаружение недостижимых объектов, но повысило бы нагрузку и паузы. Полагаться только на phantom-очистку было проще, но не давало своевременного освобождения ресурсов и не гарантировало очистку при аварийном завершении.
Выбрали явное закрытие через AutoCloseable, phantom-ссылки оставили как аварийный механизм, а обработчик очереди снабдили метриками размера очереди и времени ожидания. Это сделало освобождение предсказуемым в штатном сценарии, а утечки — диагностируемыми в резервном.
Нет. PhantomReference.get() всегда возвращает null. Очиститель должен хранить отдельно только необходимые данные: например, адрес нативного блока или дескриптор ресурса, но не сильную ссылку на Java-объект.
Нет. Постановка зависит от обнаружения недостижимости сборщиком мусора и последующей обработки ссылок. При завершении процесса фоновые потоки и сама JVM могут прекратить работу раньше, поэтому phantom-ссылки не заменяют явное закрытие ресурса.
JIT анализирует достижимость объектов по дальнейшему использованию, а не только по наличию переменной в исходном тексте. Если после последнего фактического обращения объект больше не нужен Java-коду, он может стать недостижимым ещё до возврата из метода. reachabilityFence явно продлевает достижимость до заданной точки и не позволяет очистителю освободить связанный внешний ресурс во время ещё продолжающейся операции.