Spec-Zone.ru › Java Language Specification 24

Глава 12. Исполнение

Содержание

12.1. Запуск виртуальной машины Java
12.1.1. Загрузка класса Test
12.1.2. Связывание Test: верификация, подготовка, (необязательное) разрешение
12.1.3. Инициализация теста: выполнение инициализаторов
12.1.4. Вызов Test.main
12.2. Загрузка классов и интерфейсов
12.2.1. Процесс загрузки
12.2.2. Согласованность загрузчика классов
12.3. Связывание классов и интерфейсов
12.3.1. Верификация двоичного представления
12.3.2. Подготовка класса или интерфейса
12.3.3. Разрешение символических ссылок
12.4. Инициализация классов и интерфейсов
12.4.1. Когда происходит инициализация
12.4.2. Детальная процедура инициализации
12.5. Создание новых экземпляров классов
12.6. Заключительная обработка экземпляров классов
12.6.1. Реализация заключительной обработки
12.6.2. Взаимодействие с моделью памяти
12.7. Выгрузка классов и интерфейсов
12.8. Выход из программы

В этой главе описываются действия, которые происходят во время выполнения программы. Она структурирована вокруг жизненного цикла виртуальной машины Java и классов, интерфейсов и объектов, которые образуют программу.

Виртуальная машина Java запускается, загружая указанный класс или интерфейс, а затем вызывая метод main в этом указанном классе или интерфейсе. Раздел §12.1 описывает шаги загрузки, связывания и инициализации, необходимые для выполнения main, в качестве введения в концепции этой главы. Дальнейшие разделы описывают подробности загрузки (§12.2), связывания (§12.3) и инициализации (§12.4).

Глава продолжается описанием процедур создания новых экземпляров классов (§12.5); и заключительной обработки экземпляров классов (§12.6). Она завершается описанием выгрузки классов (§12.7) и процедуры, выполняемой при выходе из программы (§12.8).

12.1. Запуск виртуальной машины Java

Виртуальная машина Java начинает выполнение, вызывая метод main определенного класса или интерфейса, передавая ему единственный аргумент — массив строк. В примерах в этом спецификации этот первый класс обычно называется Test.

Точные семантики запуска виртуальной машины Java приведены в главе 5 спецификации Спецификация виртуальной машины Java, издание Java SE 24. Здесь мы представляем общий обзор процесса с точки зрения языка программирования Java.

Способ указания начального класса или интерфейса виртуальной машине Java выходит за рамки этой спецификации, но обычно в средах, использующих командную строку, указывается полное имя класса или интерфейса в качестве аргумента командной строки, а последующие аргументы командной строки используются в качестве строк, передаваемых в метод main.

Например, в реализации UNIX команда:

java Test reboot Bob Dot Enzo

обычно запускает виртуальную машину Java, вызывая метод main класса Test (класс в безымянном пакете), передавая ему массив, содержащий четыре строки "reboot", "Bob", "Dot" и "Enzo".

Теперь мы очертим шаги, которые виртуальная машина Java может предпринять для выполнения Test, как пример процессов загрузки, компоновки и инициализации, описанных далее в последующих разделах.

12.1.1. Загрузка класса Test

Первая попытка выполнить метод main класса Test обнаруживает, что класс Test не загружен — то есть виртуальная машина Java в данный момент не содержит двоичного представления для этого класса. Виртуальная машина Java затем использует загрузчик классов для попытки найти такое двоичное представление. Если этот процесс завершится неудачно, будет выброшено исключение. Этот процесс загрузки описан далее в §12.2.

12.1.2. Компоновка Test: Проверка, Подготовка, (необязательно) Разрешение

После загрузки Test он должен быть инициализирован перед вызовом main. И Test, как и все классы и интерфейсы, должен быть прокомпонован перед инициализацией. Компоновка включает проверку, подготовку и (необязательно) разрешение. Компоновка описана далее в §12.3.

Проверка проверяет, что загруженное представление класса Test имеет правильный формат с правильной таблицей символов. Проверка также проверяет, что код, реализующий Test, подчиняется семантическим требованиям языка программирования Java и виртуальной машины Java. Если при проверке обнаружена проблема, будет выброшено исключение. Проверка описана далее в §12.3.1.

Подготовка включает выделение статического хранилища и любых структур данных, используемых внутри реализации виртуальной машины Java, таких как таблицы методов. Подготовка описана далее в §12.3.2.

Разрешение — это процесс проверки символических ссылок от класса Test к другим классам и интерфейсам путем загрузки других классов и интерфейсов, которые упомянуты, и проверки правильности ссылок.

Шаг разрешения является необязательным на момент начальной компоновки. Реализация может разрешить символические ссылки из класса или интерфейса, который компонуется очень рано, вплоть до разрешения всех символических ссылок из классов и интерфейсов, которые ссылаются дальше рекурсивно. (Это разрешение может привести к ошибкам на этих дополнительных этапах загрузки и компоновки.) Этот выбор реализации представляет собой один из крайностей и аналогичен виду "статической" компоновки, которая применялась много лет в простых реализациях языка C. (В этих реализациях скомпилированная программа обычно представляется как файл "a.out", который содержит полностью прокомпонованную версию программы, включая полностью разрешенные ссылки на библиотечные функции, используемые программой. Копии этих библиотечных функций включены в файл "a.out".)

Вместо этого реализация может выбрать разрешение символической ссылки только тогда, когда она активно используется; последовательное использование этой стратегии для всех символических ссылок представляло бы "самый ленивый" вид разрешения. В этом случае, если Test содержало несколько символических ссылок на другой класс, ссылки могут быть разрешены по одной, по мере их использования, или вообще не быть разрешены, если эти ссылки никогда не использовались во время выполнения программы.

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

Процесс разрешения описан далее в §12.3.3.

12.1.3. Инициализация main: Выполнение инициализаторов

В продолжение нашего примера виртуальная машина Java все еще пытается выполнить метод main класса Test. Это разрешается только в том случае, если класс был инициализирован (§12.4.1).

Инициализация состоит из выполнения любых инициализаторов переменных класса и статических инициализаторов класса Test в текстовом порядке. Но перед тем как Test может быть инициализирован, его непосредственный суперкласс должен быть инициализирован, а также непосредственный суперкласс его непосредственного суперкласса и так далее рекурсивно. В простейшем случае Test имеет Object в качестве неявного непосредственного суперкласса; если класс Object еще не был инициализирован, то он должен быть инициализирован перед инициализацией Test. Класс Object не имеет суперкласса, поэтому рекурсия заканчивается здесь.

Если у класса Test есть другой класс Super в качестве суперкласса, то Super должен быть инициализирован перед Test. Это требует загрузки, проверки и подготовки класса Super, если это еще не было сделано, и, в зависимости от реализации, может также включать разрешение символических ссылок из класса Super и так далее, рекурсивно.

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

Процесс инициализации описан далее в §12.4.

12.1.4. Вызов Test.main

Наконец, после завершения инициализации класса Test (в ходе которой могут произойти другие связанные загрузки, компоновки и инициализации), вызывается метод main класса Test.

Метод main должен быть объявлен public, static и void. Он должен указывать формальный параметр (§8.4.1), тип которого — массив String. Поэтому приемлемы оба следующих объявления:


public static void main(String[] args)


public static void main(String... args)

12.2. Загрузка классов и интерфейсов

Загрузка относится к процессу поиска двоичной формы класса или интерфейса с определённым именем, возможно, вычисляя его на лету, но чаще всего извлекая ранее вычисленное двоичное представление из исходного кода с помощью компилятора Java и, на основе этой двоичной формы, создавая объект для представления класса или интерфейса (§1.4).

Точные семантики загрузки приведены в главе 5 спецификации виртуальной машины Java, Java SE 24 Edition. Здесь мы представляем общий обзор процесса с точки зрения языка программирования Java.

Двоичное представление класса или интерфейса обычно имеет формат файла class, описанный в главе 4 спецификации виртуальной машины Java, Java SE 24 Edition, но возможны и другие представления, при условии что они соответствуют требованиям, указанным в §13.1.

12.2.1. Процесс загрузки

Процесс загрузки реализуется классом ClassLoader и его подклассами. Метод defineClass класса ClassLoader может использоваться для создания объектов Class из двоичных представлений в формате файла class (§1.4).

Разные подклассы ClassLoader могут реализовывать разные политики загрузки. В частности, загрузчик классов может кэшировать двоичные представления классов и интерфейсов, предварительно загружать их на основе ожидаемого использования или загружать группу связанных классов вместе. Эти действия могут быть не полностью прозрачны для работающего приложения, если, например, новая скомпилированная версия класса не будет найдена, потому что более старая версия кэширована загрузчиком классов. Тем не менее, загрузчик классов отвечает за отражение ошибок загрузки только в тех частях программы, где они могли возникнуть без предварительной загрузки или групповой загрузки.

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

  • ClassCircularityError: Запрашиваемый класс или интерфейс не может быть загружен, потому что он будет собственным суперклассом или суперинтерфейсом (§8.1.4, §9.1.3, §13.4.4).

  • ClassFormatError: Двоичные данные, которые должны представлять скомпилированный запрашиваемый класс или интерфейс, имеют неправильный формат.

  • NoClassDefFoundError: Загрузчик классов не смог найти определения запрашиваемого класса или интерфейса.

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

12.2.2. Согласованность загрузчика классов

Корректно работающие загрузчики классов сохраняют следующие свойства:

  • При одинаковом имени загрузчик классов всегда должен возвращать один и тот же объект Class.

  • Если загрузчик классов L1 делегирует загрузку класса или интерфейса C другому загрузчику L2, то для любого класса или интерфейса D, который назван прямым суперклассом C, или прямым суперинтерфейсом C, или типом поля в C, или типом формального параметра метода или конструктора в C, или типом возвращаемого значения метода в C, L1 и L2 должны возвращать один и тот же объект Class для D.

Злоумышленный загрузчик классов может нарушить эти свойства. Однако он не сможет подорвать безопасность системы типов, поскольку виртуальная машина Java защищена от этого.

Для более подробного обсуждения этих вопросов см. спецификацию виртуальной машины Java, Java SE 24 Edition, и статью «Динамическая загрузка классов в виртуальной машине Java», написанную Шенгом Лиангом и Гиладом Брахой, в «Трудах OOPSLA '98», опубликованных в «ACM SIGPLAN Notices», том 33, номер 10, октябрь 1998 года, страницы 36–44. Основной принцип проектирования языка программирования Java заключается в том, что система типов во время выполнения не может быть подорвана кодом, написанным на языке программирования Java, даже реализациями чувствительных системных классов, таких как ClassLoader.

12.3. Связывание классов и интерфейсов

Связывание — это процесс взятия двоичной формы класса или интерфейса и объединения её в состояние выполнения виртуальной машины Java, чтобы её можно было выполнить. Класс или интерфейс всегда загружается перед связыванием.

Точные семантики связывания приведены в главе 5 спецификации виртуальной машины Java, Java SE 24 издание. Здесь мы представим обзор процесса с точки зрения языка программирования Java.

В связывании участвуют три различных действия: проверка, подготовка и разрешение символических ссылок.

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

Например, реализация может выбрать разрешение каждой символической ссылки в классе или интерфейсе по отдельности только при её использовании (ленивое или отложенное разрешение) или разрешить их все сразу во время проверки класса (статическое разрешение). Это означает, что процесс разрешения может продолжаться в некоторых реализациях после инициализации класса или интерфейса.

Поскольку связывание включает выделение новых структур данных, оно может завершиться с ошибкой OutOfMemoryError.

12.3.1. Проверка двоичного представления

Проверка гарантирует, что двоичное представление класса или интерфейса структурно верно. Например, она проверяет, что каждая инструкция имеет допустимый код операции; что каждая инструкция ветвления переходит к началу другой инструкции, а не в середину инструкции; что каждый метод имеет структурно правильную сигнатуру; и что каждая инструкция подчиняется дисциплине типов виртуальной машины Java.

Если во время проверки произойдёт ошибка, то в точке программы, которая вызвала проверку класса, будет брошен экземпляр следующего подкласса класса LinkageError:

  • VerifyError: Двоичное определение класса или интерфейса не прошло набор необходимых проверок для подтверждения того, что оно подчиняется семантике языка виртуальной машины Java и не может нарушить целостность виртуальной машины Java. (См. §13.4.2, §13.4.4, §13.4.9 и §13.4.17 для некоторых примеров.)

12.3.2. Подготовка класса или интерфейса

Подготовка включает создание полей static (переменных и констант класса) для класса или интерфейса и инициализацию этих полей значениями по умолчанию (§4.12.5). Это не требует выполнения какого-либо исходного кода; явные инициализаторы для статических полей выполняются как часть инициализации (§12.4), а не подготовки.

Реализации виртуальной машины Java могут предварительно вычислить дополнительные структуры данных во время подготовки, чтобы сделать последующие операции с классом или интерфейсом более эффективными. Одной из особенно полезных структур данных является "таблица методов" или другая структура данных, которая позволяет вызывать любой метод на экземплярах класса без необходимости поиска предков во время вызова.

12.3.3. Разрешение символических ссылок

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

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

Если во время разрешения возникнет ошибка, будет выброшено исключение. Чаще всего это будет экземпляр одного из следующих подклассов класса IncompatibleClassChangeError, но это также может быть экземпляр какого-либо другого подкласса IncompatibleClassChangeError или даже экземпляр класса IncompatibleClassChangeError. Эта ошибка может быть выброшена в любой точке программы, которая использует символическую ссылку, прямо или косвенно:

  • IllegalAccessError: Встречена символическая ссылка, которая указывает на использование или присваивание поля, вызов метода или создание экземпляра класса, к которому код, содержащий ссылку, не имеет доступа, потому что поле или метод были объявлены с private, protected или пакетом доступа (не public), или потому что класс не был объявлен public в пакете, который экспортирован или открыт для кода, содержащего ссылку.

    Это может произойти, например, если поле, которое изначально объявлено public, изменено на private после того, как другой класс, ссылающийся на поле, был скомпилирован (§13.4.7); или если пакет, в котором объявлен класс public, перестаёт экспортироваться модулем после того, как другой модуль, который ссылается на класс, был скомпилирован (§13.3).

  • InstantiationError: Встречена символическая ссылка, которая используется в выражении создания экземпляра класса, но экземпляр не может быть создан, потому что ссылка, как оказалось, ссылается на интерфейс или абстрактный класс.

    Это может произойти, например, если класс, который изначально не abstract, изменяется на abstract после того, как другой класс, который ссылается на этот класс, был скомпилирован (§13.4.1).

  • NoSuchFieldError: Встречена символическая ссылка, которая ссылается на конкретное поле конкретного класса или интерфейса, но класс или интерфейс не содержит поля с таким именем.

    Это может произойти, например, если объявление поля было удалено из класса после того, как другой класс, который ссылается на поле, был скомпилирован (§13.4.8).

  • NoSuchMethodError: Встречена символическая ссылка, которая ссылается на конкретный метод конкретного класса или интерфейса, но класс или интерфейс не содержит метода с такой сигнатурой.

    Это может произойти, например, если объявление метода было удалено из класса после того, как другой класс, который ссылается на метод, был скомпилирован (§13.4.12).

Кроме того, UnsatisfiedLinkError, подкласс LinkageError, может быть выброшен, если класс объявляет native метод, для которого невозможно найти реализацию. Ошибка произойдёт, если метод будет использован или раньше, в зависимости от используемой стратегии разрешения, используемой реализацией виртуальной машины Java (§12.3).

12.4. Инициализация классов и интерфейсов

Инициализация класса состоит из выполнения его статических инициализаторов и инициализаторов для static полей (переменных класса), объявленных в классе.

Инициализация интерфейса состоит из выполнения инициализаторов для полей (констант), объявленных в интерфейсе.

12.4.1. Когда происходит инициализация

Класс или интерфейс T будет инициализирован непосредственно перед первым возникновением одного из следующих событий:

  • T — это класс, и экземпляр T создаётся.

  • Вызывается static метод, объявленный T.

  • Присваивается значение static полю, объявленному T.

  • Используется static поле, объявленное T, и это поле не является константой (§4.12.4).

При инициализации класса инициализируются его суперклассы (если они ещё не были инициализированы), а также все суперинтерфейсы (§8.1.5), которые объявляют какие-либо методы по умолчанию (§9.4.3) (если они ещё не были инициализированы). Инициализация интерфейса сама по себе не приводит к инициализации его суперинтерфейсов.

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

Вызов определённых рефлективных методов в классе Class и в пакете java.lang.reflect также вызывает инициализацию класса или интерфейса.

Класс или интерфейс не будут инициализированы при каких-либо других обстоятельствах.

Обратите внимание, что компилятор может генерировать синтетические методы по умолчанию в интерфейсе, то есть методы по умолчанию, которые не объявлены ни явно, ни неявно (§13.1). Такие методы будут вызывать инициализацию интерфейса, несмотря на то, что исходный код не даёт никаких указаний на то, что интерфейс должен быть инициализирован.

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

Тот факт, что код инициализации не ограничен, позволяет создавать примеры, где значение переменной класса можно наблюдать, когда оно всё ещё имеет своё значение по умолчанию, до вычисления его инициализирующего выражения, но такие примеры встречаются редко на практике. (Подобные примеры можно также построить для инициализации переменных экземпляра (§12.5).) В этих инициализаторах доступна вся мощь языка программирования Java; программисты должны проявить некоторую осторожность. Эта мощь накладывает дополнительную нагрузку на генераторы кода, но эта нагрузка возникнет в любом случае, потому что язык программирования Java является многопоточным (§12.4.2).

Пример 12.4.1-1. Суперклассы инициализируются до подклассов

class Super {
    static { System.out.print("Super "); }
}
class One {
    static { System.out.print("One "); }
}
class Two extends Super {
    static { System.out.print("Two "); }
}
class Test {
    public static void main(String[] args) {
        One o = null;
        Two t = new Two();
        System.out.println((Object)o == (Object)t);
    }
}

Эта программа выводит:

Super Two false

Класс One никогда не инициализируется, потому что он не используется активно и поэтому никогда не связывается. Класс Two инициализируется только после того, как его суперкласс Super был инициализирован.


Пример 12.4.1-2. Инициализируется только класс, который объявляет static поле

class Super {
    static int taxi = 1729;
}
class Sub extends Super {
    static { System.out.print("Sub "); }
}
class Test {
    public static void main(String[] args) {
        System.out.println(Sub.taxi);
    }
}

Эта программа выводит только:

1729

потому что класс Sub никогда не инициализируется; ссылка на Sub.taxi — это ссылка на поле, фактически объявленное в классе Super, и не приводит к инициализации класса Sub.


Пример 12.4.1-3. Инициализация интерфейса не инициализирует суперинтерфейсы

interface I {
    int i = 1, ii = Test.out("ii", 2);
}
interface J extends I {
    int j = Test.out("j", 3), jj = Test.out("jj", 4);
}
interface K extends J {
    int k = Test.out("k", 5);
}
class Test {
    public static void main(String[] args) {
        System.out.println(J.i);
        System.out.println(K.j);
    }
    static int out(String s, int i) {
        System.out.println(s + "=" + i);
        return i;
    }
}

Эта программа выводит:

1
j=3
jj=4
3

Ссылка на J.i относится к константе (§4.12.4); поэтому она не вызывает инициализацию I (§13.4.9).

Ссылка на K.j относится к полю, фактически объявленному в интерфейсе J, которое не является константой; это приводит к инициализации полей интерфейса J, но не к инициализации полей его суперинтерфейса I, ни к инициализации полей интерфейса K.

Несмотря на то, что имя K используется для ссылки на поле j интерфейса J, интерфейс K не инициализируется.


12.4.2. Детальная процедура инициализации

Поскольку язык программирования Java многопоточный, инициализация класса или интерфейса требует тщательной синхронизации, так как какая-то другая нить может пытаться инициализировать тот же класс или интерфейс в то же время. Также существует возможность, что инициализация класса или интерфейса может быть запрошена рекурсивно как часть инициализации этого класса или интерфейса; например, инициализатор переменной в классе A может вызвать метод несвязанного класса B, который в свою очередь может вызвать метод класса A. Реализация виртуальной машины Java отвечает за обработку синхронизации и рекурсивной инициализации, используя следующую процедуру.

Процедура предполагает, что объект Class уже был проверен и подготовлен, и что объект Class содержит состояние, указывающее одну из четырёх ситуаций:

  • Этот объект Class проверен и подготовлен, но не инициализирован.

  • Этот объект Class инициализируется некоторой конкретной нитью T.

  • Этот объект Class полностью инициализирован и готов к использованию.

  • Этот объект Class находится в состоянии ошибки, возможно, потому, что попытка инициализации завершилась неудачей.

Для каждого класса или интерфейса C существует уникальный замок инициализации LC. Сопоставление C с LC предоставлено на усмотрение реализации виртуальной машины Java. Процедура инициализации C затем выглядит следующим образом:

  1. Синхронизироваться на замке инициализации, LC, для C. Это включает ожидание, пока текущая нить сможет получить LC.

  2. Если объект Class для C указывает, что инициализация C выполняется другой нитью, то освободить LC и заблокировать текущую нить до получения информации о завершении инициализации, после чего повторить этот шаг.

  3. Если объект Class для C указывает, что инициализация C выполняется текущей нитью, то это рекурсивный запрос на инициализацию. Освободить LC и завершить обычно.

  4. Если объект Class для C указывает, что C уже инициализирован, то никаких дальнейших действий не требуется. Освободить LC и завершить обычно.

  5. Если объект Class для C находится в состоянии ошибки, то инициализация невозможна. Освободить LC и выбросить NoClassDefFoundError.

  6. В противном случае, зафиксировать факт, что инициализация объекта Class для C выполняется текущей нитью, и освободить LC.

    Затем инициализировать поля static класса C, которые являются константными переменными (§4.12.4, §8.3.2, §9.3.1).

  7. Далее, если C является классом, а не интерфейсом, то пусть SC будет его суперклассом, а SI1, ..., SIn — все суперинтерфейсы C, которые объявляют по крайней мере один метод по умолчанию. Порядок суперинтерфейсов задается рекурсивным перечислением иерархии суперинтерфейсов каждого интерфейса, непосредственно реализуемого C (в левостороннем порядке условия C). Для каждого интерфейса I, реализуемого напрямую C, перечисление рекурсивно повторяется для суперинтерфейсов I (в левостороннем порядке условия I), прежде чем вернуться к I.

    Для каждого S в списке [ SC, SI1, ..., SIn ], если S ещё не инициализирован, то рекурсивно выполняется вся эта процедура для S. При необходимости, сначала проверить и подготовить S.

    Если инициализация S завершается аварийно из-за брошенного исключения, то получить LC, пометить объект Class для C как ошибочный, уведомить все ожидающие нити, освободить LC и завершить аварийно, выбросив то же исключение, которое привело к инициализации S.

  8. Далее, определить, включены ли утверждения (§14.10) для C посредством запроса к определённому загрузчику классов.

  9. Далее, выполнить инициализаторы переменных класса и статические инициализаторы класса или инициализаторы полей интерфейса в текстовом порядке, как если бы они были одним блоком.

  10. Если выполнение инициализаторов завершается нормально, то получить LC, пометить объект Class для C как полностью инициализированный, уведомить все ожидающие нити, освободить LC и завершить эту процедуру нормально.

  11. В противном случае, инициализаторы должны были завершиться аварийно, выбросив исключение E. Если класс E не является Error или одним из его подклассов, то создать новую экземпляцию класса ExceptionInInitializerError, с E в качестве аргумента, и использовать этот объект вместо E на следующем шаге. Если новая экземпляция ExceptionInInitializerError не может быть создана из-за OutOfMemoryError, то вместо этого использовать объект OutOfMemoryError вместо E на следующем шаге.

  12. Получить LC, пометить объект Class для C как ошибочный, уведомить все ожидающие нити, освободить LC и завершить эту процедуру аварийно с причиной E или её заменой, определённой на предыдущем шаге.

Реализация может оптимизировать эту процедуру, исключив получение блокировки на шаге 1 (и освобождение на шаге 4/5), когда она может определить, что инициализация класса уже завершена, при условии, что, с точки зрения модели памяти, все отношения happens-before, которые существовали бы, если бы блокировка была получена, по-прежнему существуют, когда выполняется оптимизация.

Генераторы кода должны сохранять точки возможной инициализации класса или интерфейса, вставляя вызов описанной выше процедуры инициализации. Если эта процедура инициализации завершается нормально и объект Class полностью инициализирован и готов к использованию, то вызов процедуры инициализации больше не нужен и может быть исключён из кода — например, удалён или иначе перегенерирован.

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

12.5. Создание новых экземпляров классов

Новый экземпляр класса явно создается при вычислении выражения создания экземпляра класса (§15.9), что приводит к созданию экземпляра класса.

Новый экземпляр класса может быть неявно создан в следующих ситуациях:

  • Загрузка класса или интерфейса, содержащего строковую литерал (§3.10.5) или текстовый блок (§3.10.6) может создать новый String объект для обозначения строки, представленной строковой литерал или текстовым блоком. (Это создание объекта не произойдёт, если экземпляр String, обозначающий ту же последовательность кодовых точек Юникода, что и строка, представленная строковой литерал или текстовым блоком, ранее был интернирован.)

  • Выполнение операции, которая приводит к преобразованию в условный тип (§5.1.7). Преобразование в условный тип может создать новый объект класса-оболочки (Boolean, Byte, Short, Character, Integer, Long, Float, Double), связанный с одним из примитивных типов.

  • Выполнение оператора конкатенации строк + (§15.18.1), который не является частью константного выражения (§15.29), всегда создает новый String объект для представления результата. Операторы конкатенации строк также могут создавать временные объекты-оболочки для значения примитивного типа.

  • Вычисление выражения ссылки на метод (§15.13.3) или лямбда-выражения (§15.27.4) может потребовать создания нового экземпляра класса, реализующего функциональный интерфейс (§9.8).

В каждой из этих ситуаций определен конкретный конструктор (§8.8), который должен быть вызван со специфицированными аргументами (возможно, ни с какими) в рамках процесса создания экземпляра класса.

При создании нового экземпляра класса выделяется память для него с местом для всех переменных экземпляра, объявленных в классе, и всех переменных экземпляра, объявленных в каждом суперклассе класса, включая все переменные экземпляра, которые могут быть скрыты (§8.3).

Если доступно недостаточно места для выделения памяти для объекта, то создание экземпляра класса завершается внезапно с OutOfMemoryError. В противном случае все переменные экземпляра в новом объекте, включая те, которые объявлены в суперклассах, инициализируются их значениями по умолчанию (§4.12.5).

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

  1. Присвоить аргументы конструктора вновь созданным параметрам переменных для этого вызова конструктора.

  2. Если этот конструктор начинается с явного вызова конструктора (§8.8.7.1) другого конструктора в том же классе (используя this), то вычислить аргументы и обработать этот вызов конструктора рекурсивно, используя те же пять шагов. Если этот вызов конструктора завершается внезапно, то и эта процедура завершается по той же причине; в противном случае продолжить со шагом 5.

  3. Этот конструктор не начинается с явного вызова конструктора другого конструктора в том же классе (используя this). Если этот конструктор предназначен для класса, отличного от Object, то этот конструктор начнется с явного или неявного вызова конструктора суперкласса (используя super). Вычислить аргументы и обработать этот вызов конструктора суперкласса рекурсивно, используя те же пять шагов. Если этот вызов конструктора завершается внезапно, то и эта процедура завершается по той же причине. В противном случае продолжить со шагом 4.

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

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

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

Пример 12.5-1. Вычисление создания экземпляра

class Point {
    int x, y;
    Point() { x = 1; y = 1; }
}
class ColoredPoint extends Point {
    int color = 0xFF00FF;
}
class Test {
    public static void main(String[] args) {
        ColoredPoint cp = new ColoredPoint();
        System.out.println(cp.color);
    }
}

Здесь создается новый экземпляр ColoredPoint. Сначала выделяется память для нового ColoredPoint, чтобы содержать поля x, y и color. Затем все эти поля инициализируются их значениями по умолчанию (в данном случае, 0 для каждого поля). Далее, сначала вызывается конструктор ColoredPoint без аргументов. Так как ColoredPoint не объявляет конструкторов, неявно объявляется конструктор по умолчанию следующего вида:

ColoredPoint() { super(); }

Затем этот конструктор вызывает конструктор Point без аргументов. Конструктор Point не начинается с вызова конструктора, поэтому компилятор Java обеспечивает неявный вызов конструктора своего суперкласса без аргументов, как если бы он был написан:

Point() { super(); x = 1; y = 1; }

Следовательно, вызывается конструктор класса Object, не принимающий аргументы.

Класс Object не имеет суперклассов, поэтому рекурсия завершается здесь. Далее вызываются любые инициализаторы экземпляров и инициализаторы переменных экземпляров класса Object. Далее выполняется тело конструктора класса Object, не принимающего аргументов. Такой конструктор не объявлен в Object, поэтому компилятор Java предоставляет конструктор по умолчанию, который в этом случае:

Object() { }

Этот конструктор выполняется без эффекта и возвращается.

Далее, выполняются все инициализаторы переменных экземпляра класса Point. Как раз в этом примере, объявления x и y не предоставляют выражений инициализации, поэтому для этого шага примера никаких действий не требуется. Затем выполняется тело конструктора класса Point, устанавливающего x в 1 и y в 1.

Далее выполняются инициализаторы переменных экземпляра класса ColoredPoint. Этот шаг присваивает значение 0xFF00FF переменной color. Наконец, выполняется остальная часть тела конструктора класса ColoredPoint (часть после вызова super); в остальной части тела нет утверждений, поэтому дальнейших действий не требуется, и инициализация завершена.


Пример 12.5-2. Динамическая диспетчеризация во время создания экземпляра

class Super {
    Super() { printThree(); }
    void printThree() { System.out.println("three"); }
}
class Test extends Super {
    int three = (int)Math.PI;  // That is, 3
    void printThree() { System.out.println(three); }

    public static void main(String[] args) {
        Test t = new Test();
        t.printThree();
    }
}

Эта программа выводит:

0
3

Это демонстрирует, что вызов printThree в конструкторе класса Super не вызывает определение printThree в классе Super, а вызывает переопределенное определение printThree в классе Test. Поэтому этот метод выполняется до того, как будут выполнены инициализаторы полей класса Test, поэтому первое выводимое значение — 0, значение по умолчанию, с которым инициализируется поле three класса Test. Позже вызов printThree в методе main вызывает то же определение printThree, но к тому моменту инициализатор переменной экземпляра three уже выполнен, и поэтому выводится значение 3.


12.6. Заключительная обработка экземпляров классов

Начиная с Java SE 24, спецификация платформы Java SE допускает отключение заключительной обработки экземпляров классов в реализации платформы Java SE в преддверии удаления заключительной обработки из будущей версии платформы Java SE.

Класс Object имеет метод finalize; этот метод может быть переопределён другими классами. Конкретное определение finalize, которое может быть вызвано для объекта, называется финализатором этого объекта. Перед тем, как сборщик мусора освободит память для объекта, виртуальная машина Java вызовет финализатор этого объекта.

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

Язык программирования Java не определяет, когда будет вызван финализатор, за исключением того, что это произойдёт до повторного использования памяти объекта.

Язык программирования Java не определяет, какой поток вызовет финализатор для данного объекта.

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

Язык программирования Java не накладывает никакого порядка на вызовы методов finalize. Финализаторы могут вызываться в любом порядке или даже одновременно.

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

Несложно реализовать класс, который заставит набор методов, похожих на финализаторы, вызываться в заданном порядке для набора объектов, когда все объекты становятся недоступными. Определение такого класса предоставляется читателю в качестве упражнения.

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

Если во время финализации возникает необработанное исключение, оно игнорируется, и финализация этого объекта завершается.

Завершение конструктора объекта происходит до (§17.4.5) выполнения его метода finalize (в формальном смысле happens-before).

Метод finalize, объявленный в классе Object, не выполняет никаких действий. Тот факт, что класс Object объявляет метод finalize, означает, что метод finalize любого класса всегда может вызвать метод finalize своего суперкласса. Это всегда должно выполняться, если только программист не намерен аннулировать действия финализатора в суперклассе. (В отличие от конструкторов, финализаторы не автоматически вызывают финализатор суперкласса; такой вызов необходимо явно запрограммировать).

Для повышения эффективности реализация может отслеживать классы, которые не переопределяют метод finalize класса Object или переопределяют его тривиальным способом.

Например:


protected void finalize() throws Throwable {
    super.finalize();
}

Мы рекомендуем реализациям рассматривать такие объекты как имеющие финализатор, который не переопределён, и выполнять финализацию более эффективно, как описано в §12.6.1.

Финализатор может быть вызван явно, как и любой другой метод.

Пакет java.lang.ref описывает слабые ссылки, которые взаимодействуют со сборкой мусора и финализацией. Как и с любым API, имеющим специальные взаимодействия с языком программирования Java, реализаторы должны учитывать любые требования, накладываемые API java.lang.ref. В этой спецификации слабые ссылки не обсуждаются. Подробную информацию см. в документации API.

12.6.1. Реализация финализации

Каждый объект можно охарактеризовать двумя атрибутами: он может быть доступным, доступным для финализатора или недоступным, а также может быть незавершенным, подлежащим финализации или завершенным.

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

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

Недоступный объект не может быть достигнут ни одним из этих способов.

Незавершенный объект — это объект, для которого никогда не вызывался его финализатор автоматически.

Завершенный объект — это объект, для которого его финализатор был вызван автоматически.

Объект, подлежащий финализации, — это объект, для которого никогда не вызывался его финализатор автоматически, но виртуальная машина Java может в конечном итоге вызвать его финализатор автоматически.

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

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

Другой пример этого происходит, если значения в полях объекта хранятся в регистрах. Тогда программа может обращаться к регистрам вместо объекта и больше никогда не обращаться к объекту. Это подразумевает, что объект является мусором. Обратите внимание, что такой вид оптимизации разрешён только для ссылок на стеке, а не в куче.

Например, рассмотрите шаблон Защитника финализатора:


class Foo {
    private final Object finalizerGuardian = new Object() {
        protected void finalize() throws Throwable {
            /* finalize outer Foo object */
        }
    }
}

Защитник финализатора заставляет super.finalize быть вызванным, если подкласс переопределяет finalize и явно не вызывает super.finalize.

Если такие оптимизации разрешены для ссылок, хранящихся в куче, то компилятор Java может обнаружить, что поле finalizerGuardian никогда не читается, обнулить его, сразу собрать объект и вызвать финализатор раньше. Это противоречит намерению: программист, вероятно, хотел вызвать финализатор Foo, когда экземпляр Foo стал недоступным. Поэтому такой вид преобразования не является законным: объект внутреннего класса должен быть доступным до тех пор, пока доступен объект внешнего класса.

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

Обратите внимание, что это не предотвращает устранение синхронизации: синхронизация поддерживает объект в живом состоянии только если финализатор может синхронизироваться на нём. Поскольку финализатор выполняется в другом потоке, во многих случаях синхронизация всё равно не могла быть удалена.

12.6.2. Взаимодействие с моделью памяти

Модель памяти (§17.4) должна определять момент совершения действий, происходящих в финализаторе. В этом разделе описывается взаимодействие финализации с моделью памяти.

Каждая выполнение имеет ряд точек принятия решений о достижимости, обозначенных как di. Каждое действие либо предшествует di, либо следует за di. За исключением явно указанных случаев, порядок предшествования, описанный в этом разделе, не связан с другими порядками в модели памяти.

Если r — чтение, которое видит запись w, и r предшествует di, то w также должно предшествовать di.

Если x и y — действия синхронизации на одной переменной или мониторе, такие что so(x, y) (§17.4.4), и y предшествует di, то x также должно предшествовать di.

В каждой точке принятия решения о достижимости некоторые объекты отмечаются как недостижимые, а некоторые из них — как подлежащие финализации. Эти точки также являются точками, в которых проверяются, очередиваются и очищаются ссылки согласно правилам, указанным в документации API для пакета java.lang.ref.

Единственные объекты, которые считаются определенно достижимыми в точке di, — это те, которые можно показать достижимыми путём применения этих правил:

  • Объект B определённо достижим в точке di из полей static, если существует запись w1 в поле static v класса C, где значение, записанное w1, — ссылка на B, класс C загружен достижимым загрузчиком классов, и не существует записи w2 в v, такая что hb(w2, w1) не истинно, а w1 и w2 предшествуют di.

  • Объект B определённо достижим из A в точке di, если существует запись w1 в элементе v A, где значение, записанное w1, — ссылка на B, и не существует записи w2 в v, такая что hb(w2, w1) не истинно, а w1 и w2 предшествуют di.

  • Если объект C определённо достижим из объекта B, а объект B определённо достижим из объекта A, то C определённо достижим из A.

Если объект X отмечен как недостижимый в точке di, то:

  • X не должен быть определённо достижим в точке di из полей static;

  • Все активные использования X в потоке t, которые следуют за di, должны происходить в вызове финализатора для X или в результате чтения ссылки на X потоком t, которое следует за di;

  • Все чтения, которые следуют за di и видят ссылку на X, должны видеть записи в элементы объектов, которые были недостижимы в di, или видеть записи, которые произошли после di.

Действие a является активным использованием X тогда и только тогда, когда истинно хотя бы одно из следующих утверждений:

  • a читает или записывает элемент X

  • a блокирует или разблокирует X, и существует действие блокировки на X, которое происходит после вызова финализатора для X

  • a записывает ссылку на X

  • a является активным использованием объекта Y, и X определённо достижим из Y

Если объект X отмечен как подлежащий финализации в точке di, то:

  • X должен быть отмечен как недостижимый в точке di;

  • di должна быть единственной точкой, где X отмечен как подлежащий финализации;

  • Действия, которые происходят после вызова финализатора, должны следовать за di.

12.7. Выгрузка классов и интерфейсов

Реализация языка программирования Java может выгружать классы.

Класс или интерфейс могут быть выгружены только в том случае, если его определяющий загрузчик классов может быть удалён сборщиком мусора, как обсуждается в §12.6.

Классы и интерфейсы, загруженные загрузчиком базового уровня, не могут быть выгружены.

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

Однако, если класс или интерфейс C был выгружен, в то время как его определяющий загрузчик был потенциально достижимым, то C может быть перезагружен. Нельзя гарантировать, что этого не произойдёт. Даже если к классу не ссылается ни один другой загруженный класс, на него может ссылаться какой-либо класс или интерфейс D, который ещё не загружен. Загрузка D определяющим загрузчиком C может привести к перезагрузке C.

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

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

Необходимо также обосновать, почему безопасно выгружать класс C, если его определяющий загрузчик классов может быть удалён. Если определяющий загрузчик может быть удалён, значит, к нему не может быть живых ссылок (это включает ссылки, которые не являются активными, но могут быть восстановлены финализаторами). Это, в свою очередь, может быть истинным только в том случае, если не может быть активных ссылок на какие-либо классы, определённые этим загрузчиком, включая C, ни из их экземпляров, ни из кода.

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

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

12.8. Выход программы

Программа состоит из одной или нескольких потоков выполнения. Поток является либо потоком-не-демоном, потоком-демоном, либо обработчиком завершения.

Подробную информацию о том, как потоки получают статус демона, и о регистрации обработчиков завершения, см. в спецификациях API Thread и Runtime.

Поток завершается, если (i) его метод run завершается нормально, или (ii) его метод run завершается внезапно, и соответствующий обработчик необработанных исключений (§11.3) завершается нормально или внезапно. Без кода для выполнения поток завершил выполнение и, следовательно, не имеет текущего метода (JVMS §2.5.1).

Программа завершается, когда произошла одна из следующих ситуаций:

  1. Все ее потоки-не-демоны завершились, и все обработчики завершения, которые впоследствии были запущены виртуальной машиной Java, если таковые имеются, завершились.

  2. Поток вызвал System.exit или Runtime.exit, и все обработчики завершения, которые впоследствии были запущены виртуальной машиной Java, если таковые имеются, завершились.

  3. Поток вызвал Runtime.halt. (В этом случае обработчики завершения не запускаются.)

  4. Реализация виртуальной машины Java распознала внешнее событие, запрашивающее завершение виртуальной машины Java, и все обработчики завершения, которые впоследствии были запущены виртуальной машиной Java, если таковые имеются, завершились.

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

  5. Произошло внешнее событие, с которым реализация виртуальной машины Java не может справиться. (В этом случае обработчики завершения не запускаются.)

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

При завершении программы любой поток-демон или поток-не-демон, который еще не завершен, не выполнит дальнейший код Java. Текущий метод потока не завершается нормально или внезапно. Никакая часть finally любого метода в потоке не выполняется, а также ни один обработчик необработанных исключений.

Если завершение программы происходит потому, что поток вызвал Runtime.halt во время выполнения обработчиков завершения, то помимо потоков-демонов и потоков-не-демонов любой обработчик завершения, который еще не завершен, не выполнит дальнейший код Java.

За исключением этой необычной ситуации, связанной с Runtime.halt, завершение программы зависит от завершения любых обработчиков завершения, которые были запущены. Обоснование следующее. Когда количество потоков-не-демонов падает до нуля или поток вызывает System.exit или Runtime.exit, вероятно, программа больше не имеет работы и переходит к выходу; однако, у программы могут быть и другие потоки, выполняющие вспомогательные задачи, и было бы нежелательно останавливать их в самом начале. Обработчики завершения позволяют программе плавно прервать и завершить такие потоки в приложении-специфическим образом; следовательно, программа еще не завершена, если обработчики завершения все еще выполняются.

Приложения нативных языках могут использовать API вызова JNI для создания и уничтожения виртуальной машины Java таким образом, что программа Java, начавшая выполнение в методе main начального класса (§12.1), завершается, как описано в первой ситуации выше.

© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API