- PVSM.RU - https://www.pvsm.ru -

Как я пытался понять смысл метода finalize

Недавно меня пригласили в одну компанию на собеседование на должность 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/