Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Остерегайтесь методов flnalize



Методы finalize непредсказуемы, часто опасны и, как правило, не нужны. Их использование может привести к странному поведению программы, низкой производи­тельности и проблемам с переносимостью. Метод finalize имеет лишь несколько облас­тей применения (см. ниже), а главное правило таково: следует избегать методов finalize.

Программистов, пишущих на С++, следует предостеречь в том, что нельзя рассматривать методы finalize как аналог деструкторов в С++. В С++ деструктор _ это обычный способ утилизации ресурсов, связанных с объектом, обязательное допол­нение к конструктору. В языке программирования java, когда объект становится недо­ступен, очистку связанной с ним памяти осуществляет сборщик мусора. Со стороны же программиста никаких специальных действий не требуется. В С++ деструкторы используются для освобождения не только памяти, но и других ресурсов системы. В языке программирования java для этого обычно применяется блок  try-finally.

Нет гарантии, что метод finalize будут вызван немедленно [JLS, 12.6]. С момента, когда объект становится недосТупен, и до момента выполнения метода finalize может пройти сколь угодно длительное время. Это означает, что с помощью метода finalize нельзя выполнять никаких операций, критичных по времени. Например, будет серьезной ошибкой ставить процедуру закрытия открытых файлов в зависимость от метода finalize, поскольку дескрипторы открытых файлов - ресурс ограниченный. Если из-за того, что jVM медлит с запуском методов finalize, открытыми будут оставаться много файлов, программа может завершиться с ошибкой, поскольку ей не удастся открыть новые файлы.

Частота запуска методов finalize в первую очередь определяется алгоритмом сборки мусора, который существенно меняется от одной реализации jVM к другой. Точно так же может меняться и поведение программы, работа которой зависит от частоты вызова методов finalize. Вполне возможно, что программа будет превосходно работать с jVM, на которой проводится ее тестирование, а затем позорно даст сбой на JVM, которую предпочитает ваш самый важный заказчик.

Запоздалый вызов методов finalize - не только теоретическая проблема. Создав ДЛЯ какого-либо класса метод finalize, в ряде случаев можно спровоцировать произвольную задержку при удалении его экземпляров. Один мой коллега недавно отлаживал приложение СИI, которое было рассчитано на длительное функционирование, но таинственно умирало с ошибкой OutOfMemoryError. Анализ показал, что в момент смерти приложение в очереди на удаление стояли тысячи графических объектов, ждавших лишь вызова метода finalize и утилизации. К несчастью, поток утилизации выполнялся с меньшим приоритетом, чем другой поток того же приложения, а потому удаление объектов не могло осуществиться в том же темпе, в каком они становились доступны для удаления. Спецификация языка Java не определяет, в каком из потоков

 

 

19

 

будут выполняться методы finalize. Поэтому нет иного универсального способа предот­вратить проблемы такого рода, кроме как воздерживаться от использования методов finalize.

Спецификация языка Java не только не дает поручительства, что методы finalize будут вызваны быстро, она не гарантирует, что они вообще будут вызваны. Вполне возможно, что программа завершится, так и не вызвав метод finalize для некоторых объектов, ставших недоступными. Следовательно, обновление критического фикси­руемого (persistent) состояния не должно зависеть от метода finalize. Например, ставить освобождение фиксируемой блокировки разделяемого ресурса, такого как база данных, в зависимость от метода finalize - верный способ привести всю вашу распре­деленную систему к сокрушительному краху.

Не соблазняйтесь методами System.gc и System.runFinalization. Они могут повысить вероятность запуска утилизации, но не гарантируют этого. Единственные методы, требующие гарантированного удаления,- это System.runFinalizersOnExit и его вредный близнец Runtime.runFinalizersOnExit. Эти методы некорректны и признаны устаревшими.

Стоит обратить внимание еще на один момент: если в ходе утилизации возникает необработанная исключительная ситуация (exception), она игнорируется, а утилизация этого объекта прекращается [JLS, 12.6]. Необработанная исключительная ситуация может оставить объект в испорченном состоянии. И если другой поток попытается воспользоваться испорченным объектом, результат в определенной мере может быть непредсказуем. Обычно необработанная исключительная ситуация завершает поток и выдает распечатку стека, однако в методе finalize этого не происходит; он даже не выводит предупреждений.

Так чем же заменить метод finalize для класса, чьи объекты инкапсулируют ресурсы, требующие завершения, такие как файлы или потоки? Создайте метод для прямого завершения и потребуйте, чтобы клиенты класса вызывали этот метод для каждого экземпляра, ,когда он им больше не нужен. Стоит упомянуть об одной детали: экземпляр сам должен следить за тем, был ли он завершен. Метод прямо го заверше­ния должен делать запись в некое м закрытом поле о том, что объект более не является действительным. Остальные методы класса должны проверять это поле и иницииро­вать исключительную ситуацию IllegalStateException, если их вызывают после того, как данный объект был завершен.

Типичный пример метода прямого завершения - метод close в InputStгеаm и OutputStгеаm. Еще один пример - метод саnсеl из jауа.util.Timer, который нужным образом меняет состояние объекта, заставляя поток (thread), связанный с экземпляром Timer, аккуратно завершить свою работу. Среди примеров из пакета jауа.awt - Graphics.dispose и Window.dispose. На эти методы редко обращают внимание, что сказывается на производительности программы. То же самое касается метода Image. flush, который освобождает все ресурсы, связанные с экземпляром Image, но оставляет последний в таком состоянии, что его еще можно использовать, выделив вновь необходимые ресурсы.

 

20

 

 

Методы прямого завершения часто используются в сочетании с конструк­цией try-finally для обеспечения гарантированного завершения. Вызов метода прямого завершения из оператора finally гарантирует, что он будет выполнен, если даже при работе с объектом возникнет исключительная ситуация:

// Блок try-finally гарантирует вызов метода завершения

Foo foo = new Foo( ... );

tгу {

// Делаем то, что необходимо сделать с foo

}finally {

foo.terminate(); // Метод прямого Завершения

}

Зачем же тогда вообще нужны методы finalize? У них есть два приемлемых применения. Первое - они выступают в роли "страховочной сетки" в том случае, если владелец объекта забывает вызвать метод прямо го завершения. Нет гарантии, что метод finalize будет вызван своевременно, однако в тех случаях (будем надеяться, редких), когда клиент не выполняет свою часть соглашения, т. е. не вызывает метод прямого завершения, критический ресурс лучше 'освободить поздно, чем никогда. Три класса, представленных как пример использования метода прямого завершения (InputStream, OutputStream и Тiтeг), тоже имеют методы finalize, которые применяется в качестве страховочной сетки, если соответствующие методы завершения не были вызваны.

Другое приемлемое применение методов finalize связано с объектами, имеющими "местных партнеров" (native peers). Местный партнер - это местный объект (native object), к которому обычный объект обращается через машинно-зависимые методы. Поскольку местный партнер не является обычным объектом, сборщик мусора о нем не знает, и когда утилизируется обычный партнер, утилизировать местного партнера он не может. Метод finalize является приемлемым посредником для решения этой задачи при условии, что местный партнер не содержит критических ресурсов. Если же местный партнер содержит ресурсы, которые необходимо освободить немедленно, данный класс должен иметь метод прямого завершения. Этот метод завершения обязан делать все, что необходимо для освобождения соответствующего критического ресурса. Метод завершения может быть машинно-зависимым методом либо вызывать таковой.

Важно отметить, что здесь нет автоматического связывания методов finalize ("finalizer chaining"). Если в классе (за исключениемОЬjесt) есть метод finalize, но в подклассе он был переопределен, то метод finalize в подклассе должен вызывать. Метод finalize, из суперкласса. Вы должны завершить подкласс в блоке try, а затем в Соответствующем блоке finally вызвать метод finalize суперкласса. Тем самым

 

 

21

 

 

гарантируется, что метод finalize суперкласса будет вызван, даже если при завершении подкласса инициируется исключительная ситуация, и наоборот:

 

// Ручное связывание метода finalize

protected void finalize() throws Throwable try {

//' Ликвидируем состояние подкласса

finally {

super.f1nal1ze();

}

}

Если разработчик подкласса переопределяет метод finalize суперкласса, но забывает вызвать его "вручную" (или не делает этого из вредности), метод finalize суперкласса так и не будет вызван. Защититься от такого беспечного или вредного подкласса можно ценой создания некоего дополнительного объекта для каждого объекта, подле­жащего утилизации. Вместо того чтобы размещать метод finalize в классе, требующем утилизации, поместите его в анонимный класс (статья 18), единственным назначением которого будет утилизация соответствующего экземпляра. для каждого экземпляра контролируемого класса создается единственный экземпляр анонимного класса, назы­ваемый хранителем утилизации (fjnalizer guardian). Контролируемый экземпляр содержит в закрытом экземпляре поля единственную в системе ссылку на хранителя утилизации. Таким образом, хранитель утилизации становится доступен для удаления в момент утилизации контролируемого им экземпляра. Когда хранитель утилизирует­ся, он выполняет процедуры, необходимые для ликвидации контролируемого им эк­земпляра, как если бы его метод finalize был методом контролируемого класса:

 

// Идиома хранителя утилизации ( Finalizer Guardian )

public class Foo {

// Единственная задача этого объекта - утилизировать

// внешний объект Foo

private final Object finalizerGuardian = new Object() protected void finalize() throws Throwable

/ / Утилизирует внешний объект Foo

} ;

// Остальное опущено

}

Заметим, что у открытого класса Foo нет метода finalize (за исключением·тривиаль­ного, унаследованного от класса Object), а потому не важно, был ли в методе f1nalize подкласса вызов метода super.finalize или нет. Возможность использования этой

 

 

22

 

 

методики следует рассмотреть для каждого открытого расширяемого класса, имеющего метод finalize.

Подведем итоги. Не применяйте методы finalize, кроме как в качестве страхо­вочной сетки или для освобождения некритических местных ресурсов. В тех редких случаях, когда вы должны использовать метод finalize, не забывайте делать вызов Super.finalize. И последнее: если вам необходимо связать метод finalize с откры­тым классом без модификатора finaI, подумайте о применении хранителя утилизации, чтобы быть уверенным в том, что утилизация будет выполнена, даже если в подклассе в методе finalize не будет вызова super.finalize.

 

23

Глава 3


Поделиться:



Последнее изменение этой страницы: 2019-04-11; Просмотров: 218; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.023 с.)
Главная | Случайная страница | Обратная связь