- PVSM.RU - https://www.pvsm.ru -
Недавно меня пригласили в одну компанию на собеседование на должность Java-программиста. На собеседовании зашла речь о работе метода finalize. Я имел лишь поверхностное представление о работе этого метода и не смог дать достойного его описания интервьюверам. Поэтому после собеседования я должен был провести работу над ошибками во всем разобраться.
Мои знания ограничивались тем, что метод finalize вызывается в момент, когда сборщик мусора начинает утилизировать объект. И я не совсем понимал для чего он служит. Я думал, что это что-то типа деструктора, в котором можно освобождать определенные ресурсы после того, как они больше не нужны, причем даже ресурсы, которые хранятся в других объектах, что не верно.
Так вот, первое, что требовалось понять — назначение.
Предназначен этот метод для автоматического освобождения системных ресурсов, занимаемых объектом, на котором будет данный метод вызван. Это кажется удобным, что бы не помнить постоянно, например, что мы должны закрыть соединение с каким-то ресурсом, когда оно больше не требуется.
Не стоит полагаться на finalize для чистки данных. Во-первых, нет гарантии, что он будет вызван, т.к. где-то может остаться ссылка на объект. Во-вторых, нет гарантии на то, в какое время будет вызван метод. Это связано с тем, что после того, как объект становится доступным для сборки и, если в нем переопределен метод finalize, то он не вызывается сразу, а помещается в очередь, которая обрабатывается специально созданным для этого потоком. Стоит отметить, что в очередь на финализацию попадают только те объекты, в которых переопределен метод finalize.
Есть вероятность, что этот метод не будет вызван совсем. Это может произойти в момент, когда объект уже станет доступным для сборщика мусора и программа завершит свою работу.
Интересной особенностью метода является то, что он может снова сделать объект доступным, присвоив this какой-нибудь переменной, хотя так делать не рекомендуется, т.к. при восстановлении объекта, повторно finalize вызван не будет
Может случиться еще один редкий момент. У нас есть класс A, в котором реализован метод finalize. Мы создаем класс B extends A, в котором забываем про finalize. Объекты класса B содержат в себе много данных. Когда объекты классы B становятся ненужными, они попадут в очередь на финализацию и определенное время еще будут занимать память, вместо того, что бы миновать этой очереди и сразу утилизироваться.
Еще одним недостатком является то, что надо помнить про вызов finalize-метода супер-класса, если мы переопределяем его. Разработчик не вызовет — никто не вызовет.
Исключения, брошенные в методе finalize, не обрабатываются потоком-финализатором, т.е. данный стектрейс скорее всего нельзя будет отследить.
Есть один способ быть уверенным, что finalize-методы были запущены для объектов, доступных для сборки: вызвать System.runFinalization() [1] или Runtime.getRuntime().runFinalization(). Выход из метода осуществляется только тогда, когда все доступные методы объектов для финализации будут выполнены
Для себя я сделал вывод, что пользоваться этим методом без особой надобности не стоит, а случаи этой особой надобности на моей двух-с-половиной-летней практике пока не встречались.
Лучше вместо finalize писать методы типа close в java.io и вызывать их в блоке finally. Недостатком является то, что разработкик должен помнить, что ресурс после использования нужно закрыть. На помощь тут нам пришла Java SE 7 со своими try-with-resources [2]
Но ведь этот метод для чего-то есть. Где и как его можно использовать? Есть ли примеры использования?
Finalize можно использовать как последний шанс закрыть ресурс, но никогда как первая или единственная попытка. Т.е. в дополнение к тому, что клиент может вызвать, например, метод close на объекте, представляющем ресурс. А может и забыть. Тогда можно попытаться ему помочь. Так сделано, например, в классе FileInputStream.java:
protected void finalize() throws IOException {
if ((fd != null) && (fd != FileDescriptor.in)) {
/*
* Finalize should not release the FileDescriptor if another
* stream is still using it. If the user directly invokes
* close() then the FileDescriptor is also released.
*/
runningFinalize.set(Boolean.TRUE);
try {
close();
} finally {
runningFinalize.set(Boolean.FALSE);
}
}
}
Данный подход часто используется в библиотеках Java.
PS. Прошу не принимать написанное за чистую правду. Я написал лишь то, как я понял прочитанный материал. Приму с радостью любые дополнения, критику и исправления.
Список использованной литературы:
How to Handle Java Finalization's Memory-Retention Issues [3]
Java Finalize method call [4]
java.lang. Class Object [5]
10 points on finalize method in Java – Tutorial Example [6]
Habrahabr. Finalize и Finalizer [7]
Автор: shurik2533
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/36540
Ссылки в тексте:
[1] System.runFinalization(): http://docs.oracle.com/javase/6/docs/api/java/lang/System.html#runFinalization%28%29
[2] try-with-resources: http://javarevisited.blogspot.ru/2011/09/arm-automatic-resource-management-in.html
[3] How to Handle Java Finalization's Memory-Retention Issues: http://www.oracle.com/technetwork/articles/javase/finalization-137655.html
[4] Java Finalize method call: http://stackoverflow.com/questions/2506488/java-finalize-method-call
[5] java.lang. Class Object: http://docs.oracle.com/javase/6/docs/api/java/lang/Object.html
[6] 10 points on finalize method in Java – Tutorial Example: http://javarevisited.blogspot.ru/2012/03/finalize-method-in-java-tutorial.html
[7] Habrahabr. Finalize и Finalizer: http://habrahabr.ru/post/144544/
[8] Источник: http://habrahabr.ru/post/183344/
Нажмите здесь для печати.