Как избежать утечек ресурсов и реализовать финализацию зависимостей в библиотеке Dependency Injection?
Что проверяют
Понимаете ли вы, кто владеет объектом и отвечает за его закрытие. Контейнер, который создал соединение или пул потоков, обязан его и освободить, иначе ресурс живёт до остановки процесса. Следом обычно спрашивают, что происходит с prototype-бинами и в каком порядке закрываются зависимости.
Как отвечать
Начните с принципа: освобождает тот, кто создал. Для своей DI-библиотеки это складывается в несколько правил:
- учёт созданного: контейнер запоминает каждый синглтон, который реализует
AutoCloseable, в порядке создания; - обратный порядок:
close()контейнера закрывает объекты от последнего к первому, чтобы сервис закрылся раньше пула соединений, от которого он зависит; - сбой одного не останавливает остальных: исключение из
close()ловится, следующие ресурсы всё равно закрываются, а ошибки собираются черезaddSuppressedи выбрасываются одним исключением в конце; - идемпотентность: повторный
close()ничего не делает; - частичная сборка: если конструктор пятого бина упал, четыре уже созданных закрываются сразу;
- хук остановки:
Runtime.addShutdownHookкак страховка, но основной путь это явныйclose()или try-with-resources вокруг контейнера.
Хорошо сослаться на Spring как на эталон: ConfigurableApplicationContext закрывает синглтоны через @PreDestroy, DisposableBean и выведенный метод close или shutdown у @Bean, а prototype-бины отдаёт вызывающему коду и не уничтожает.
Где спотыкаются
Предлагают finalize() или WeakReference. finalize() помечен к удалению (JEP 421), а сборщик мусора не гарантирует, когда и будет ли вообще вызван код очистки: дескрипторы и соединения закончатся раньше. Cleaner годится только как запасная сеть.
Хранят ссылки на все prototype-объекты, чтобы потом их закрыть. Контейнер становится вечным держателем короткоживущих объектов, и это уже утечка памяти.
Внедряют короткоживущую зависимость в синглтон напрямую. Синглтон захватывает один экземпляр навсегда, и ресурс, рассчитанный на один запрос, живёт всё приложение. Контейнер должен либо запрещать такую связь на старте, либо подставлять прокси или Provider<T>, как делают Spring и Guice.