Содержание | Предыдущая | Следующая


ГЛАВА 12

Выполнение

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

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

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

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

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

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

java Test reboot Bob Dot Enzo

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

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

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

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

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

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

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

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

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

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

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

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

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

12.1.3 Инициализация класса Test: выполнение инициализаторов

В нашем примере, виртуальная машина все еще готовится к выполнению метода 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 из Тest.

Метод main должен быть объявлен как public, static, и void. В качестве единственного параметра должен выступать массив из строк.

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

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

Двоичный формат класса или интерфейса - обычно формат файла класса, взятый из описания виртуальной машины языка Ява, но возможны и другие форматы, если они удовлетворяют требованиям, определенным в §13.1. Метод defineClass (§20.14.3) класса ClassLoader может использоваться для создания объектов Class из двоичных представлений в формате файла класса.

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

Эта внутренняя таблица позволяет осуществить допуск при проверке (§12.3.1), для этих целей идентифицируются два класса или интерфейса, имеющие то же самое имя и тот же самый загрузчик класса. Это позволяет классу быть проверенным активно или пассивно без загрузки всех классов и интерфейсов, которые им используются. Загрузчики прошедшего проверку класса поддерживают это свойство: присвоив одно и то же имя дважды, хороший загрузчик класса должен в любой момент возвращать тот же самый объект класса. Но без внутренней таблицы, несовершенный загрузчик классов мог бы нарушить это свойство и повредить защиту системы типов языка Ява. Основной принцип построения языка Ява - то, что систему типов нельзя нарушить кодом, написанным на Яве, ни даже реализациями таких чувствительных системных классов как ClassLoader (§20.14) и SecurityManager (§20.17).

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

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

Процесс загрузки реализуется классом ClassLoader (§20.14) и его подклассами. Различные подклассы ClassLoader могут выполнять различные виды загрузки. В частности загрузчик класса может кэшировать двоичные представления классов и интерфейсов, основываясь на предсказании их ожидаемого использования, или загружать группу связанных классов вместе. Эти действия могут не быть полностью прозрачны для выполняемой прикладной Ява-программы, если, например, вновь созданная версия класса не найдена потому, что более старая версия кэшируется загрузчиком класса. Задача загрузчика классов, однако, реагировать на ошибки загрузки только в тех точках программы, где ошибки могут возникнуть и при отсутствии предсказания и групповой загрузки.

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

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

12.2.2 Загрузка: замечания для генерации кода

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

12.3 Компоновка классов и интерфейсов

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

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

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

Так как компоновка включает распределение новых структур данных, то может быть сгенерирована OutOfMemoryError.

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

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

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

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

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

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

Реализации языка Ява должны обнаружить следующую ошибку при подготовке:

Если такая ошибка обнаружена, то экземпляр AbstractMethodError должен быть сгенерирован в том месте программы на Яве, где класс вызывался для подготовки.

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

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

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

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

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

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

12.3.4 Компоновка: замечания для генерации кода

Символические ссылки внутри группы типов могут быть разрешены даже до загрузки группы (§12.2.2) в реализации, которая использует специальный (нестандартный) двоичный формат (§13.1). Это соответствует традиционной практике “редактирования связей”. Даже если таковой не имеется, реализация Явы имеет множество других возможностей. Можно разрешить все символические ссылки из типа в месте первого действия компоновки на типе, или задержать разрешение каждой символической ссылки на первое использование этой ссылки.

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

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

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

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

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

Тип T класса или интерфейса будет инициализирован при первом активном использовании, которое произойдет если:

Все другие использования типа - пассивные.

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

Как показано в примере в §8.5, можно привести неограниченное число примеров, где может наблюдаться значение переменной класса, которое она приобретает по умолчанию до вычисления выражение инициализации, но такие примеры на практике редки. (Такие примеры могут также создавать, например, переменную инициализацию; см. пример в конце §12.5). Ява предусматривает полную мощность языка в этих инициализаторах; программисты должны позаботится лишь о некоторых предосторожностях. Проблемы, связанные с мощностью должны решать генераторы кодов, но они возникли бы в любом случае, потому что Ява - язык для параллельного программирования (§12.4.3).

До инициализации класса инициализируются его суперклассы, если они предварительно не были инициализированы.

Таким образом, программа Test:

 

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.

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

 


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.

Инициализация интерфейса, сама, требует инициализации суперинтерфейса. Таким образом, программа Test

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 - это ссылка на поле, которое является константой времени компиляции; следовательно, I может быть неинициализированным. Ссылка на K.j - ссылка на поле, фактически объявленное в интерфейсе J, которое не является константой времени компиляции; это вызывает инициализацию полей интерфейса J, но не суперинтерфейса I и не интерфейса K. Несмотря на тот факт, что имя K используется, чтобы обратиться к полю j интерфейса J, интерфейс K не используется активно.

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

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

Процедура для инициализации класса или интерфейса следующая:

  1. Синхронизация (§14.17) на объекте Class, представляющем класс или интерфейс, который будет инициализирован. Это включает ожидание текущим объектом получения замка этого объекта §17.13.
  2. Если инициализация для класса или интерфейса производится некоторыми другими потоками, то происходит ожидание (§20.1.6) на этом объекте Class (который временно освобождает замок). Когда текущий поток выходит из процесса ожидания, повторяется этот шаг.
  3. Если инициализация для класса или интерфейса производится текущим потоком, то должен быть рекурсивный запрос инициализации. Освобождается замок на объекте Class и процесс заканчивается обычным образом.
  4. Если класс или интерфейс уже был инициализирован, то дальнейших действий не требуется. Освобождается замок на объекте Class и завершается обычным образом.
  5. Если объект Class находится в ошибочном состоянии, то инициализация становится невозможной. Освобождается замок на объекте Class, и генерируется NoClassDefFoundError.
  6. Иначе, устанавливается факт, что инициализация объекта из Class теперь выполняется текущим потоком и освобождается замок на объекте Class.
  7. Затем, если объект Class представил класс раньше интерфейса, и суперкласс этого класса еще не был инициализирован, то рекурсивно выполняется вся эта процедура для суперкласса. В случае необходимости, сначала проверяется и подготавливается суперкласс. Если инициализация суперкласса резко завершается из-за сгенерированного исключения, то блокируется этот объект Class, маркируется ошибочным, сообщается всем ожидающим потокам (§20.1.10), освобождается замок, и резко завершается, генерируя то же исключение, которое является результатом инициализации суперкласса.
  8. Затем выполняются инициализаторы переменных класса и статические инициализаторы класса, или инициализаторы полей интерфейса, в порядке, соответствующем текстовому описанию, как если бы они были единым блоком, за исключением того, что переменные final и поля интерфейсов, чьи значения константы времени компиляции инициализированы раньше (§8.3.2.1, §9.3.1, §13.4.8).
  9. Если выполнение инициализаторов завершается обычным образом, то блокируется этот объект Class, маркируется как полностью инициализированный, сообщается всем ожидающим потокам (§20.1.10), отпускается блокировка, и эта процедура завершается обычным образом.
  10. Иначе, инициализаторы должны резко завершить генерацией некоторого исключения E. Если класс E - не Error или один из его подклассов, то создается новый экземпляр класса ExceptionInInitializerError, имея E в качестве параметра, и этот объект используется вместо E при следующем шаге. Но если новый экземпляр ExceptionInInitializerError не может быть создан из-за того, что происходит OutOfMemoryError, то взамен используется экземпляр OutOfMemoryError вместо E при следующем шаге.
  11. Блокируется объект Class, маркируется как ошибочный, сообщается всем ожидающим потокам (§20.1.10), отпускается блокировка, и завершается резко эта процедура по причине E или с ее заменой как определено в предыдущем шаге.

(Из-за ошибок в некоторых ранних реализациях Явы, исключение при инициализации класса игнорировалось и ExceptionInInitializerError не генерировалось.)

12.4.3 Инициализация: замечания для генерации кода

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

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

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

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

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

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

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

  1. Передаются аргументы для конструктора вновь созданным переменным-параметрам для вызова этого конструктора.
  2. Если этот конструктор начинается с явного вызова другого конструктора в том же самом классе (используя this), то вычисляются аргументы и происходит процесс рекурсивного вызова конструктора с использованием тех же пяти шагов. Если вызов этого конструктора завершается преждевременно, то и эта процедура завершается преждевременно по той же самой причине; иначе продолжается с шага 5.
  3. Конструктор не начинается с явного вызова другого конструктора в том же классе (используя this). Если этот конструктор - для класса не такого как Object, то конструктор начнется с явного или неявного вызова конструктора суперкласса (используя super). Вычисляются параметры и вызов конструктора суперкласса, который рекурсивно использует те же пять шагов. Если вызов того конструктора завершается преждевременно, то и эта процедура завершается предевременно по той же самой причине. Иначе, продолжается с шага 4.
  4. Выполняются инициализаторы переменных экземпляра для этого класса, присваивая их значения соответствующим переменным экземпляра, в порядке слева направо, в котором они появляются в исходном тексте класса. Если выполнение любого из этих инициализаторов кончается исключением, то никакие дальнейшие инициализаторы не обрабатываются и эта процедура завершается преждевременно с этим же исключением. Иначе, продолжается с шага 5. (В некоторых ранних реализациях Явы компилятор неправильно упустил код для инициализации поля если выражение инициализатора поля было константным выражением, чье значение было равно заданному по умолчанию значению инициализации для типа.)
  5. Выполняется оставшаяся часть этого конструктора. Если выполнение завершается преждевременно, то и эта процедура завершается преждевременно по той же самой причине. Иначе, эта процедура завершается обычным образом.

В примере:

 

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 не начинается с вызова конструктора, поэтому компилятор обеспечивает неявный вызов конструктора суперкласса без параметров, как если бы было написано:

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

Следовательно, вызывается конструктор для Object без параметров.

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

Object() { }

Этот конструктор выполняется и происходит возврат.

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

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

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

 

class Super {

class Super {
	Super() { printThree(); }
	void printThree() { System.out.println("three"); }
}

class Test extends Super {
	int indiana = (int)Math.PI;													// That is, 3

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

выводится:

 

0

3

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

См. §8.6 для более детального рассмотрения объявлений конструктора.

12.6 Финализация элементов класса

Класс Object имеет protected метод, названный finalize (§20.1.11); этот метод может быть замещен другими классами. Специфическое определение finalize метода, который может вызываться для объекта, называется finalizer(финализатор) этого объекта. Прежде, чем память для объекта восстановится сборщиком мусора, виртуальная машина языка Ява вызовет финализатор этого объекта.

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

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

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

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

protected void finalize() throws Throwable {

	super.finalize();

}

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

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

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

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

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

Объект unfinalised никогда не имел автоматически вызванного финализатора; объект finalised имел автоматический вызванный финализатор. Финализирующийся объект никогда не имел автоматически вызванного финализатора, но виртуальная машина языка Ява может со временем вызвать финализатор автоматически.

Цикл жизни объекта повинуется следующей диаграмме переходов, где мы сокращаем "finalizer-reachable" как "f-reachable":

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Когда объект только что создан (A), он является достижимым и финализированным.

Поскольку ссылки на объект отбрасываются при выполнении программы, объект, который был достижимым может стать достижимым для финализации (B, C, D) или недостижимым (E, F). (Обратите внимание, что объект достижимый для финализации никогда не становится непосредственно недостижимым; он становится недостижимым когда вызывается финализатор, из которого он может быть достигнут, как объяснено ниже.)

Если виртуальная машина языка Ява обнаруживает, что нефинализированный объект стал finalizer-reachable или unreachable, то она может маркировать объект как finalizable (G, H); кроме того, если объект был unreachable, тогда он становится finalizer-reachable (H).

Если виртуальная машина языка Ява обнаруживает, что finalized объект стал unreachable, она может утилизировать память, занятую объектом, потому что объект никогда больше не станет достижимым (I).

В любое время, виртуальная машина языка Ява может брать любой финализирующийся объект, маркировать его как finalized, и затем вызывать его finalise-метод в каком-то потоке. Это дает возможность объекту стать финализированным и достижимым (J, K), а другим объектам, которые были достижимыми для финализации снова стать достижимыми ( L, M, N).

finalizable объект не может также быть unreachable; он может быть достигнут, потому что его finalizer со временем может быть вызван, после чего поток, управляющий finalizer будет иметь доступ к объекту, как к this §15.7.2. Таким образом, фактически существуют только восемь возможных состояний объекта.

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

Явный вызов finalizer игнорирует текущее состояние объекта и не изменяет состояние объекта из unfinalized или finalizable на finalized.

Если класс не замещает метод finelize класса Object (или замещает его только тривиальным образом, как описано выше), то если экземпляры класса становятся недостижимыми, они могут быть утилизированы немедленно до того как станет ожидаемым второе определение, что они стали недостижимыми. Эта стратегия обозначена штриховой линией со стрелкой (O) в диаграмме переходов.

Программисты, работающие на Яве должны также знать что finalizer может вызываться автоматически, даже если он - достижим, при финализации (§12.9); кроме того, финализатор может также вызываться явно как обычный метод. Следовательно, мы рекомендуем сохранять простой структуру finalise методов и чтобы они программировались защищенными для работы в любых ситуациях.

12.6.2 Вызовы финализаторов не упорядочены.

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

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

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

12.7 Финализация классов

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

static void classFinalize() throws Throwable { . . . }

то этот метод будет вызываться перед тем, как класс будет выгружен (§12.8). Подобно методу finalise для объектов, этот метод будет автоматически вызван только один раз. Этот метод может быть объявлен private, protected, или public.

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

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

Класс может не быть разгружен, пока любой его экземпляр - все еще reachable (§12.6). Класс или интерфейс может не быть выгружен, в то время как объект Class, который представляет его - все еще reachable.

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

12.9 Завершение работы виртуальной машины

Виртуальная машина языка Ява завершает все действия в одном из двух случаев:

Ява-прграмма может определять, что финализаторы всех объектов, которые имеют финализаторы, и все классы, которые имеют finalizers класса, которые еще не были автоматически вызваны будут выполнены до завершения работы виртуальной машины. При этом вызывается метод runFinalizersOnExit класса System с параметром true. Значение по умолчанию определено так, чтобы не выполнить финализаторы при завершении работы, и это состояние может быть получено после вызова runFinalizersOnExit с параметром false. Вызов метода runFinalizersOnExit разрешен только, если вызывающий разрешит exit, а иначе отклонен SecurityManager (§20.17).


Содержание | Предыдущая | Следующая