Финализатор

редактировать

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

Термин «финализатор» "в основном используется в объектно-ориентированных и функциональных языках, которые используют сборку мусора, архетипом которого является Smalltalk. Это контрастирует с «деструктором », который представляет собой метод, вызываемый для завершения в языках с детерминированным временем жизни объекта, архетипично C ++. Как правило, они являются исключительными - в языке будут либо финализаторы (при автоматическом сборке мусора), либо деструкторы (при ручном управлении памятью), но в редких случаях язык может иметь и то, и другое, как в C ++ / CLI и D, а в случае подсчета ссылок (вместо отслеживания сборки мусора) терминология меняется. В техническом использовании термин «финализатор» также может использоваться для обозначения деструкторов, так как они также выполняют финализацию, и здесь показаны некоторые более тонкие различия - см. терминологию. Термин «окончательный» также используется для обозначения класса, который не может быть унаследован ; это не связано.

Содержание
  • 1 Терминология
  • 2 Использование
  • 3 Синтаксис
  • 4 Реализация
  • 5 Проблемы
  • 6 Управление ресурсами
    • 6.1 Детерминированное и недетерминированное время жизни объекта
  • 7 Воскрешение объекта
  • 8 Контраст с инициализацией
  • 9 Соединение с finally
  • 10 История
  • 11 См. Также
  • 12 Примечания
  • 13 Ссылки
  • 14 Дополнительная литература
  • 15 Внешние ссылки
Терминология

Терминология «финализатор» и «завершение» по сравнению с «деструктором» и «уничтожением» варьируется между авторами и иногда неясна.

В общем случае деструктор - это метод, детерминированно вызываемый при уничтожении объекта, а архетип - деструкторы C ++; в то время как финализатор вызывается сборщиком мусора недетерминированно, а архетип - методы Java finalize.

Для языков, которые реализуют сборку мусора с помощью подсчета ссылок, терминология варьируется, в некоторых языках, таких как Objective-C и Perl, использующих «деструктор», и в других языках, например как Python с использованием «финализатора» (согласно спецификации Python собирает мусор, но в эталонной реализации CPython, начиная с версии 2.0, используется комбинация подсчета ссылок и сборки мусора). Это отражает тот факт, что подсчет ссылок приводит к полудетерминированному времени жизни объекта: для объектов, которые не являются частью цикла, объекты уничтожаются детерминированно, когда счетчик ссылок падает до нуля, но объекты, которые являются частью цикла, уничтожаются недетерминированно., как часть отдельной формы сборки мусора.

В определенном узком техническом употреблении «конструктор» и «деструктор» являются терминами уровня языка, означающими «методы, определенные в классе», а «инициализатор» и «финализатор» являются терминами уровня реализации, что означает: методы, вызываемые при создании или уничтожении объекта ». Так, например, в исходной спецификации языка C # упоминались «деструкторы», хотя C # собирает мусор, но спецификация для Common Language Infrastructure (CLI) и реализация ее среды выполнения как Common Language Runtime (CLR), именуемая «финализаторами». Это отражено в примечаниях комитета по языку C #, которые частично гласят: «Компилятор C # компилирует деструкторы в... [вероятно] финализатор экземпляра [ы]». Эта терминология сбивает с толку, и поэтому в более поздних версиях спецификации C # метод уровня языка называется "финализаторами".

Еще один язык, в котором нет такого различия в терминологии, - D. Хотя классы D собираются сборщиком мусора, их функции очистки называются деструкторами.

Использование

Завершение в основном используется для очистки, освобождения памяти или других ресурсов: для освобождения памяти, выделенной с помощью ручного управления памятью ; для очистки ссылок, если используется подсчет ссылок (уменьшение количества ссылок); для высвобождения ресурсов, особенно в идиоме получение ресурсов - инициализация (RAII); или отменить регистрацию объекта. Объем финализации значительно различается между языками, от обширной финализации в C ++, который имеет ручное управление памятью, подсчет ссылок и детерминированное время жизни объектов; часто без финализации в Java, которая имеет недетерминированное время жизни объектов и часто реализуется с помощью сборщика мусора. Также возможно, что явная (определяемая пользователем) финализация будет незначительной или совсем не будет, но будет значительная неявная финализация, выполняемая компилятором, интерпретатором или средой выполнения; это обычное дело в случае автоматического подсчета ссылок, как в эталонной реализации Python CPython или в Автоматический подсчет ссылок в реализации Apple Objective-C, которые оба автоматически прерывают ссылки во время финализации. Финализатор может включать произвольный код; особенно сложным является автоматическое возвращение объекта в пул объектов .

Освобождение памяти во время финализации распространено в таких языках, как C ++, где ручное управление памятью является стандартным, но также происходит в управляемых языках, когда память была выделена вне управляемая куча (внешне по отношению к языку); в Java это происходит с объектами Java Native Interface (JNI) и ByteBufferв New I / O (NIO). Последнее может вызвать проблемы из-за того, что сборщик мусора не может отслеживать эти внешние ресурсы, поэтому они не будут собираться достаточно агрессивно и могут вызвать ошибки нехватки памяти из-за исчерпания неуправляемой памяти - этого можно избежать, если обработать собственные память в качестве ресурса и с использованием шаблона удаления , как описано ниже.

Финализаторы обычно и гораздо менее необходимы, и гораздо реже используются, чем деструкторы. Они гораздо менее необходимы, потому что сборка мусора автоматизирует управление памятью, и гораздо реже используются, потому что они обычно не выполняются детерминированно - они могут не вызываться своевременно или даже вообще, а среда выполнения не может быть предсказуемым - и, следовательно, любая очистка, которая должна выполняться детерминированным образом, должна вместо этого выполняться каким-либо другим методом, чаще всего вручную с помощью шаблона удаления . Примечательно, что и Java, и Python не гарантируют, что финализаторы когда-либо будут вызваны, и, следовательно, на них нельзя полагаться при очистке.

Из-за того, что программист не контролирует их выполнение, обычно рекомендуется избегать финализаторов для любых, кроме самых тривиальных, операций. В частности, операции, часто выполняемые в деструкторах, обычно не подходят для финализаторов. Обычный анти-шаблон - это писать финализаторы, как если бы они были деструкторами, что одновременно и не нужно, и неэффективно из-за различий между финализаторами и деструкторами. Это особенно часто встречается среди программистов на C ++, поскольку деструкторы широко используются в идиоматическом C ++, следуя идиоме Resource Acquisition Is Initialization (RAII).

Синтаксис

Языки программирования, использующие финализаторы, включают C ++ / CLI, C#, Clean, Go, Java и Python. Синтаксис значительно зависит от языка.

В Java финализатор - это метод с именем finalize, который переопределяет метод Object.finalize.

В Python финализатор - это метод называется __del__.

В Perl финализатор - это метод с именем DESTROY.

. В C # финализатор (называемый «деструктором» в более ранних версиях стандарта) - это метод, имя которого является именем класса с префиксом ~, как в ~ Foo- это тот же синтаксис, что и деструктор C ++, и эти методы изначально назывались «деструкторами» по аналогии с C ++, несмотря на различное поведение, но были переименованы в "финализаторы" из-за возникшей путаницы.

В C ++ / CLI, который имеет как деструкторы, так и финализаторы, деструктор - это метод, имя которого совпадает с именем класса с ~с префиксом, как в ~ Foo(как в C #), а финализатор - это метод, имя которого является именем класса с префиксом !, как в ! Foo.

В Go финализаторы применяются к одному указателю путем вызова функции runtime.SetFinalizerв стандартная библиотека.

Реализация

Финализатор вызывается, когда объект собирается сборщиком мусора - после того, как объект стал мусором (недоступен), но до того, как его память будет освобождена. Финализация происходит недетерминированно, по усмотрению сборщика мусора и может никогда не произойти. Это контрастирует с деструкторами, которые вызываются детерминированно, как только объект больше не используется, и вызываются всегда, за исключением случая неконтролируемого завершения программы. Финализаторами чаще всего являются методы экземпляра из-за необходимости выполнять операции с объектами.

Сборщик мусора также должен учитывать возможность воскрешения объекта. Чаще всего это делается путем первого выполнения финализаторов, затем проверки, были ли воскрешены какие-либо объекты, и, если да, отмены их уничтожения. Эта дополнительная проверка потенциально дорогостоящая - простая реализация повторно проверяет весь мусор, если хотя бы один объект имеет финализатор - и, таким образом, замедляет и усложняет сборку мусора. По этой причине объекты с финализаторами могут собираться реже, чем объекты без финализаторов (только в определенных циклах), что усугубляет проблемы, вызванные зависимостью от своевременного завершения, такие как утечки ресурсов.

Если объект воскресает, возникает следующий вопрос: будет ли его финализатор вызван снова, когда он будет уничтожен в следующий раз - в отличие от деструкторов, финализаторы потенциально вызываются несколько раз. Если для воскрешенных объектов вызываются финализаторы, объекты могут многократно воскрешать себя и быть неразрушаемыми; это происходит в реализации Python CPython до Python 3.4, а также в языках CLR, таких как C #. Чтобы избежать этого, на многих языках, включая Java, Objective-C (по крайней мере, в последних реализациях Apple) и Python из Python 3.4, объекты финализируются не чаще одного раза, что требует отслеживания, был ли объект еще завершен.

В других случаях, особенно в языках CLR, таких как C #, финализация отслеживается отдельно от самих объектов, и объекты могут повторно регистрироваться или отменяться для завершения.

Проблемы

Финализаторы могут вызвать значительное количество проблем, поэтому ряд властей настоятельно не одобряют их. Эти проблемы включают в себя:

  • Финализаторы не могут быть вызваны своевременно или вообще, поэтому на них нельзя полагаться для сохранения состояния, высвобождения ограниченных ресурсов или выполнения каких-либо других важных действий.
  • Финализаторы могут приводит к воскрешению объекта, что часто является ошибкой программирования и сама вероятность которого значительно замедляет и усложняет сборку мусора.
  • Финализаторы запускаются на основе сборки мусора, которая обычно основана на управляемой памяти давление - они не запускаются в случае нехватки других ресурсов и, следовательно, не подходят для управления другими дефицитными ресурсами.
  • Финализаторы не запускаются в указанном порядке и не могут полагаться на инварианты классов (поскольку они могут относиться к другим объектам, которые уже были финализированы).
  • Медленные финализаторы могут задерживать другие финализаторы.
  • Исключения в финализаторах обычно не могут быть обработаны, потому что финализатор запускается в неопределенная среда, и может игнорироваться или вызывать неконтролируемый d завершение программы.
  • Финализаторы могут ссылаться и случайно финализировать живые объекты, нарушая программные инварианты.
  • Финализаторы могут вызывать проблемы синхронизации, даже в других последовательных (однопоточных) программах, поскольку финализация может быть выполняется одновременно (конкретно, в одном или нескольких отдельных потоках).
  • Финализаторы могут вызвать взаимоблокировку, если используются механизмы синхронизации, такие как блокировки, из-за того, что они не выполняются в указанном порядке и, возможно, выполняются одновременно.
  • Финализаторы, которые запускаются во время завершения программы, не могут полагаться на обычную среду выполнения и, следовательно, могут выйти из строя из-за неверных предположений - по этой причине финализаторы часто не запускаются во время завершения.

Кроме того, финализаторы могут не работать из-за объект остается доступным после того, как ожидается, что они будут мусором, либо из-за ошибок программирования, либо из-за неожиданной достижимости. Например, когда Python перехватывает исключение (или исключение не перехватывается в интерактивном режиме), он сохраняет ссылку на кадр стека, в котором возникло исключение, что сохраняет объекты, на которые ссылается этот кадр стека, в действии.

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

Управление ресурсами

Распространенным анти-шаблоном является использование финализаторов для освобождения ресурсов по аналогии с Resource Acquisition Is Initialization (RAII) идиома C ++: получить ресурс в инициализаторе (конструкторе) и освободить его в финализаторе (деструкторе). Это не работает по ряду причин. По сути, финализаторы могут никогда не вызываться, и даже если они вызваны, они могут не вызываться своевременно - таким образом, использование финализаторов для освобождения ресурсов обычно вызывает утечки ресурсов. Кроме того, финализаторы не вызываются в предписанном порядке, в то время как ресурсы часто нужно освобождать в определенном порядке, часто в порядке, обратном тому, в котором они были получены. Кроме того, поскольку финализаторы вызываются по усмотрению сборщика мусора, они часто будут вызываться только при нехватке управляемой памяти (когда доступно мало управляемой памяти), независимо от давления ресурсов - если скудные ресурсы удерживаются мусором, но их много доступной управляемой памяти, сборка мусора может не произойти, поэтому эти ресурсы не будут восстановлены.

Таким образом, вместо использования финализаторов для автоматического управления ресурсами в языках со сборкой мусора необходимо вручную управлять ресурсами, обычно с использованием шаблона удаления . В этом случае ресурсы все еще могут быть получены в инициализаторе, который вызывается явно при создании экземпляра объекта, но освобождается в методе удаления. Метод dispose может быть вызван явно или неявно языковыми конструкциями, такими как в C # с использованием, try-with-resources в Java или в Python с.

. случаях для освобождения ресурсов используются как шаблон удаления, так и финализаторы. Это в основном встречается в языках CLR, таких как C #, где финализация используется в качестве резервной копии для удаления: когда ресурс получен, получающий объект ставится в очередь для завершения, так что ресурс освобождается при уничтожении объекта, даже если ресурс не выпущен путем ручной утилизации.

Детерминированное и недетерминированное время жизни объекта

В языках с детерминированным временем жизни объекта, особенно в C ++, управление ресурсами часто осуществляется путем привязки времени жизни владения ресурсом к времени жизни объекта, получения ресурсов во время инициализации и их освобождения во время доработки; это известно как инициализация получения ресурсов (RAII). Это гарантирует, что владение ресурсами является инвариантом класса и что ресурсы высвобождаются незамедлительно при уничтожении объекта.

Однако в языках с недетерминированным временем жизни объекта - которые включают все основные языки со сборкой мусора, такие как C #, Java и Python - это не работает, потому что финализация может быть несвоевременной или может не произойти вообще, и, таким образом, ресурсы могут не высвобождаться в течение длительного времени или даже вообще, вызывая утечку ресурсов. Вместо этого на этих языках ресурсы обычно управляются вручную с помощью шаблона dispose : ресурсы могут быть получены во время инициализации, но освобождаются путем вызова метода dispose. Тем не менее, использование финализации для освобождения ресурсов на этих языках является распространенным анти-шаблоном , и если вы забудете вызвать dispose, это все равно вызовет утечку ресурсов.

В некоторых случаях оба метода комбинируются, используя явный метод удаления, но также освобождая все еще удерживаемые ресурсы во время завершения в качестве резервной копии. Это обычно встречается в C # и реализуется путем регистрации объекта для завершения всякий раз, когда ресурс приобретается, и подавления завершения всякий раз, когда ресурс освобождается.

Воскрешение объекта

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

Далее, воскрешение объекта означает, что объект не может быть уничтожен, а в патологических случаях объект всегда может воскресить себя во время завершения, сделав себя неразрушимым. Чтобы предотвратить это, некоторые языки, такие как Java и Python (начиная с Python 3.4), завершают объекты только один раз и не завершают воскрешенные объекты. Конкретно это делается путем отслеживания того, был ли объект завершен на индивидуальной основе. Objective-C также отслеживает финализацию (по крайней мере, в последних версиях Apple) по аналогичным причинам, рассматривая воскрешение как ошибку.

Другой подход используется в .NET Framework, особенно в C # и Visual Basic.NET, где финализация отслеживается «очередью», а не объект. В этом случае, если предоставляется указанный пользователем финализатор, по умолчанию объект финализируется только один раз (он ставится в очередь для завершения при создании и удаляется из очереди после завершения), но это можно изменить, вызвав GC.модуль. Завершение можно предотвратить, вызвав GC.SuppressFinalize, который выводит объект из очереди, или повторно активировав его, вызвав GC.ReRegisterForFinalize, который ставит объект в очередь. Они особенно используются при использовании финализации для управления ресурсами в качестве дополнения к шаблону удаления или при реализации пула объектов .

Контраст с инициализацией

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

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

Переменные обычно инициализируются в начале своего жизненного цикла, но не завершаются в конце своего времени существования - хотя, если переменная имеет объект в качестве своего значения, объект может быть завершен. В некоторых случаях переменные также финализируются: расширения GCC позволяют финализировать переменные.

Соединение с finally

Как отражено в названии, конструкция «finalization» и finallyвыполняет схожие цели: выполнение некоторого заключительного действия, обычно очистка, после того, как что-то еще закончилось. Они отличаются тем, когда они возникают - предложение finallyвыполняется, когда выполнение программы покидает тело связанного предложения try- это происходит во время раскрутки стека, и, таким образом, существует стек ожидающих finallyпо порядку - в то время как финализация происходит, когда объект уничтожается, что происходит в зависимости от метода управления памятью, и в целом есть просто набор объектов, ожидающих завершения - часто в куче - которые нуждаются в не происходит ни в каком определенном порядке.

Однако в некоторых случаях они совпадают. В C ++ уничтожение объекта является детерминированным, и поведение предложения finallyможет быть произведено за счет наличия локальной переменной с объектом в качестве значения, область видимости которого является блоком, соответствующим телу Предложение try- объект завершается (уничтожается), когда выполнение выходит из этой области, точно так же, как если бы было предложение finally. По этой причине C ++ не имеет конструкции finally- разница в том, что финализация определяется в определении класса как метод деструктора, а не на сайте вызова в предложении finally..

И наоборот, в случае предложения finallyв сопрограмме, как и в генераторе Python, сопрограмма может никогда не завершиться - только когда-либо уступая - и, следовательно, в при обычном выполнении предложение finallyникогда не выполняется. Если интерпретировать экземпляры сопрограммы как объекты, то предложение finallyможно рассматривать как финализатор объекта и, таким образом, может быть выполнено, когда экземпляр будет собран сборщиком мусора. В терминологии Python определение сопрограммы - это функция генератора, а ее экземпляр - итератор генератора, и, таким образом, предложение finallyв функции генератора становится финализатором в итераторах генератора, созданных из этой функции.

История

Понятие финализации как отдельного шага в разрушении объекта восходит к Montgomery (1994), по аналогии с более ранним различием инициализации при создании объекта в Мартин и Оделл (1992). В литературе до этого момента для этого процесса использовалось «разрушение», без различия финализации и освобождения, а в языках программирования того периода, таких как C ++ и Perl, использовался термин «разрушение». Термины «завершение» и «завершение» также используются во влиятельной книге Шаблоны проектирования (1994). Появление Java в 1995 году содержало методы finalize, которые популяризировали этот термин и связали его со сборкой мусора, и языки с этого момента обычно проводят это различие и используют термин «завершение», особенно в контексте мусора. коллекция.

.

См. Также
Примечания
Ссылки
Дополнительная литература
Внешние ссылки
Последняя правка сделана 2021-05-20 04:10:17
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте