Философия Java

         

Вы должны выполнять очистку


Для очистки объекта пользователь этого объекта должен вызвать метод очистки в том месте, где это не обходимо. Эти слова хорошо понятны, но это приводит к концепции деструктора из C++, что все объекты разрушаются. Или вернее, что все объекты должны разрушаться. Если объекты C++ создаются локально (т.е. в стеке, что невозможно в Java), то разрушение происходит в месте закрытия фигурной скобки того блока, в котором объект был создан. Если объект был создан с помощью new (как в Java), деструктор вызывается, когда программист вызовет оператор delete из C++ (который не существует в Java). Если программист C++ забудет вызвать delete, деструктор никогда не вызовется, и вы получите утечку памяти, в сочетании со всеми другими прелестями отсутствия очистки объектов. Этот род ошибок могут быть очень трудными в обнаружении.

В противоположность этому, Java не позволяет создание локальных объектов — вы всегда должны использовать new. Но в Java нет “delete” для выполнения освобождения объекта, так как сборщик мусора освобождает хранилище за вас. Так что, с точки зрения простоты, вы могли бы сказать, что по причине сборки мусора Java не имеет деструкторов. Однако в процессе чтения книги вы увидите, что присутствие сборщика мусора не снимает требования в существовании таких средств, как деструкторы. (И вы никогда не должны вызывать finalize( ) напрямую, так что это не подходящий путь для решения.) Если вы некоторый род очистки выполняет освобождение других ресурсов, отличных от хранилища, вы должны все-таки явно вызвать соответствующий метод Java, который является эквивалентом деструктора C++, без каких-либо соглашений.

Одна из вещей, для которых finalize( ) может быть полезна, это наблюдение за процессом сборки мусора. Следующий пример показывает вам, что происходит и подводит итог предыдущего описания сборки мусора:

//: c04:Garbage.java

// Демонстрация сборщика мусора

// и финализации

class Chair { static boolean gcrun = false; static boolean f = false; static int created = 0; static int finalized = 0; int i; Chair() { i = ++created; if(created == 47) System.out.println("Created 47"); } public void finalize() { if(!gcrun) { // Первый раз вызывается finalize():


gcrun = true; System.out.println( "Beginning to finalize after " + created + " Chairs have been created"); } if(i == 47) { System.out.println( "Finalizing Chair #47, " + " Setting flag to stop Chair creation"); f = true; } finalized++; if(finalized >= created) System.out.println( "All " + finalized + " finalized"); } }

public class Garbage { public static void main(String[] args) { // До тех пор, пока флаг не установлен,

// создаются Chairs и Strings:

while(!Chair.f) { new Chair(); new String("To take up space"); } System.out.println( "After all Chairs have been created:\n" + "total created = " + Chair.created + ", total finalized = " + Chair.finalized); // Необязательные аргументы форсируют

// сборку мусора и финализацию

if(args.length > 0) { if(args[0].equals("gc") || args[0].equals("all")) { System.out.println("gc():"); System.gc(); } if(args[0].equals("finalize") || args[0].equals("all")) { System.out.println("runFinalization():"); System.runFinalization(); } } System.out.println("bye!"); } } ///:~

Приведенная выше программа создает множество объектов Chair, и в некоторой точке, после начала работы сборщика мусора, программа прекращает создание Chair. Так как сборщик мусора может запуститься в любой момент, вы не можете точно знать, когда он стартует, поэтому есть флаг, называемый gcrun для индикации того, произошел ли запуск сборщика мусора. Второй флаг f дает возможность объекту Chair сообщить в цикл main( ), что он должен остановить создание объектов. Оба эти флага устанавливаются в finalize( ), который вызывается при сборке мусора.

Две другие static переменные: created и finalized, следят за числом созданных Chair и за числом объектов, подвергшихся финализации сборщиком мусора. И, наконец, каждый Chair имеет свой собственный (не-static) int i, который следит за тем, какой порядковый номер имеет объект. Когда финилизируется Chair с номером 47, флаг устанавливается в true, чтобы инициировать остановку процесса создания Chair.



Все это происходит в цикле main( )

while(!Chair.f) { new Chair(); new String("To take up space"); }

Вы можете удивиться, как этот цикл вообще может завершиться, так как внутри нет ничего, что изменяло бы значение Chair.f. Однако finalize( ), в конечном счете, сделает это, когда будет финализован объект номер 47.

Создание объекта String в каждой итерации просто приводит к дополнительному выделению места для ускорения запуска сборщика мусора, который начнет действовать, когда будет озабочен количеством доступной памяти.

Когда вы запускаете программу, вы передаете аргумент командной строки “gc,” “finalize,” или “all”. Аргумент “gc” приведет к вызову метода System.gc( ) (для форсирования работы сборщика мусора). Использование “finalize” приведет к вызову System.runFinalization( ), который, теоретически, является причиной того, что не финализированные объекты будут финализированы. А “all” станет причиной вызова обоих методов.



Поведение этой программы в версии первой редакции этой книги показывает, что все проблемы сборки мусора и финализации эволюционировали, и большинство эволюции произошло за закрытыми дверями. Фактически, в то время, когда вы читаете это, поведение программы может снова изменится.

Если вызван System.gc( ), то финализация происходит для всех объектов. Это не было необходимо с предыдущей реализацией JDK, хотя документация заявляла обратное. Кроме того, вы увидите, что кажется, что нет каких-то различий, произошел ли вызов System.runFinalization( ).

Однако вы увидите, что если System.gc( ) вызывается после того, как все объекты будут созданы и работа с ними будет завершена, то будут вызваны все методы финализации. Если вы не вызываете System.gc( ), то только некоторые из объектов будут финализированы. В Java 1.1 метод System.runFinalizersOnExit( ) был введен, чтобы являться причиной, заставляющей запускать всех методов финализации при выходе из программы, но при этом в дизайне появлялось много ошибок, поэтому метод устарел и был заменен. Это дает представление о том, какие искания предпринимали разработчики Java в попытках решить проблемы сбора мусора и финализации. Мы можем только надеяться, что эти вещи достаточно хорошо разработаны в Java 2.

Предыдущая программа показывает, что обещание, что все финализации всегда выполняются, справедлива только, если вы явно навязываете, чтобы это происходило. Если вы не вызываете System.gc( ), на выходе вы получите примерно следующее:

Created 47 Beginning to finalize after 3486 Chairs have been created Finalizing Chair #47, Setting flag to stop Chair creation After all Chairs have been created: total created = 3881, total finalized = 2684 bye!

Таким образом, не все финализации вызываются до того, как программа завершится. Если вызван System.gc( ), это приведет к финализации и разрушению всех объектов, которые более не используются в этот момент.

Помните, что ни сборка мусора, ни финализация не гарантирована. Если Виртуальная Java Машина (JVM) не приближается к переполнению памяти, то она (что очень мудро) не тратит время на освобождение памяти с помощью сборки мусора.


Содержание раздела