Spec-Zone .ru
спецификации, руководства, описания, API
|
Содержание | Предыдущий | Следующий | Индекс | Спецификация Виртуальной машины JavaTM |
ГЛАВА 7
Виртуальная машина Java разрабатывается, чтобы поддерживать язык программирования Java. Выпуски JDK Sun и Java, 2 SDK содержат обоих компилятор из исходного кода, записанного в языке программирования Java набору команд виртуальной машины Java, и системе времени выполнения, которая реализует виртуальную машину Java непосредственно. Понимание, как один компилятор использует виртуальную машину Java, полезно для возможного разработчика компилятора, так же как для одной попытки понять виртуальную машину Java непосредственно.
Хотя эта глава концентрируется на компиляции исходного кода, записанного в языке программирования Java, виртуальная машина Java не предполагает, что инструкции, которые это выполняет, были сгенерированы от такого кода. В то время как было много усилий, нацеленных на компиляцию других языков к виртуальной машине Java, текущая версия виртуальной машины Java не была разработана, чтобы поддерживать широкий диапазон языков. Некоторые языки могут быть размещены справедливо непосредственно виртуальной машиной Java. Другие языки могут быть реализованы только неэффективно.
Отметьте, что термин "компилятор" иногда используется, обращаясь к транслятору от набора команд виртуальной машины Java к набору команд определенного ЦП. Одним примером такого транслятора является своевременное (JIT) генератор кода, который генерирует специфичные для платформы инструкции только после того, как код виртуальной машины Java был загружен. Эта глава не решает проблемы, связанные с генерацией кода, только связанные с компиляцией исходного кода, записанного в языке программирования Java инструкциям виртуальной машины Java.
javac
компилятор в выпуске 1.0.2 JDK Sun генерирует для примеров. Код виртуальной машины Java пишется на неофициальном "ассемблере виртуальной машины", выведенном Sun javap
утилита, распределенная с программным обеспечением JDK и Java 2 SDK. Можно использовать javap
генерировать дополнительные примеры скомпилированных методов. Формат примеров должен быть знакомым любому, кто считал код блока. Каждая инструкция принимает форму
<Индекс> индекс кода операции инструкции в массиве, который содержит байты кода виртуальной машины Java для этого метода. Альтернативно, <индекс> может считаться байтовым смещением с начала метода. <Код операции> мнемосхема для кода операции инструкции, и нуль, или больше <operandN> является операндами инструкции. Дополнительное <комментарий> дается в синтаксисе комментария конца строки:<index> <opcode> [<operand1> [<operand2>...]] [<comment>]
Часть материала в комментариях испускается8 bipush 100 // Pushint
constant100
javap
; остальное предоставляется авторами. <Индекс>, снабжающий каждую инструкцию предисловием, может использоваться в качестве цели инструкции передачи управления. Например, goto 8 передач инструкции управляет к инструкции по индексу 8. Отметьте, что фактические операнды инструкций передачи управления виртуальной машиной Java являются смещениями от адресов кодов операций тех инструкций; эти операнды выводятся на экран javap
(и показываются в этой главе), как более легко смещения чтения в их методы. Мы снабжаем предисловием операнд, представляющий индекс пула константы этапа выполнения с хешем, подписывают и следуют инструкциям комментарием, идентифицирующим элемент пула константы этапа выполнения, на который ссылаются, как в
или10 ldc #1 // Pushfloat
constant100.0
В целях этой главы мы не волнуемся об определении деталей, таких как размеры операнда.9 invokevirtual #4 // MethodExample.addTwo(II)I
spin
метод просто вращается вокруг пустого for
цикл 100 раз:
Компилятор мог бы скомпилироватьvoid spin() {
int i;
for (i = 0; i < 100; i++) {
; // Loop body is empty
}
}
spin
к
Виртуальная машина Java со стековой организацией с большинством операций, берущих один или более операндов от стека операнда текущего фрейма виртуальной машины Java или продвигающих результаты назад на стек операнда. Новый фрейм создается каждый раз, когда метод вызывается, и с этим создается новый стек операнда и набор локальных переменных для использования тем методом (см. Раздел 3.6, "Фреймы"). В любой точке вычисления, таким образом, вероятно, будет много фреймов и одинаково много стеков операнда на поток управления, соответствуя многим вложенным вызовам метода. Только стек операнда в текущем фрейме является активным.Methodvoid
spin()
0 iconst_0 // Pushint
constant0
1 istore_1 // Store into local variable 1 (i
=0
) 2 goto 8 // First time through don't increment 5 iinc 1 1 // Increment local variable 1 by 1 (i++
) 8 iload_1 // Push local variable 1 (i
) 9 bipush 100 // Pushint
constant100
11 if_icmplt 5 // Compare and loop if less than (i
<100
) 14 возвратитесь //Возвратvoid
когда сделано
Набор команд виртуальной машины Java отличает типы операнда при использовании отличных байт-кодов для операций на его различных типах данных. Метод spin
работает только на значениях типа int
. Инструкции в его скомпилированном коде, выбранном, чтобы работать на введенных данных (iconst_0, istore_1, iinc, iload_1, if_icmplt), все специализируются для типа int
.
Эти две константы в spin
, 0
и 100
, продвигаются на стек операнда, используя две различных инструкции. 0
продвигается, используя iconst_0 инструкцию, одно из семейства iconst _ <i> инструкции. 100
продвигается, используя bipush инструкцию, которая выбирает значение, которое она продвигает как непосредственный операнд.
Виртуальная машина Java часто использует в своих интересах вероятность определенных операндов (int
константы-1, 0, 1, 2, 3, 4 и 5 в случае iconst _ <i> инструкции), делая те операнды, неявные в коде операции. Поскольку iconst_0 инструкция знает, что собирается продвинуть int
0
, iconst_0 не должен сохранить операнд, чтобы сказать это, какое значение продвинуть, и при этом это не должно выбрать или декодировать операнд. Компиляция нажатия 0
как bipush 0 было бы корректно, но сделает скомпилированный код для spin
на один байт дольше. Простая виртуальная машина также провела бы дополнительное время, выбирая и декодируя явный операнд каждый раз вокруг цикла. Использование неявных операндов делает скомпилированный код более компактным и эффективным.
int
i
в spin
сохранен как локальная переменная виртуальной машины Java 1. Поскольку большинство инструкций виртуальной машины Java работает на значениях, вытолканных от стека операнда, а не непосредственно на локальных переменных, инструкции, которые передают значения между локальными переменными и стеком операнда, распространены в коде, скомпилированном для виртуальной машины Java. У этих операций также есть специальная поддержка в наборе команд. В spin
, значения передаются и от локальных переменных, используя istore_1 и iload_1 инструкции, каждая из которых неявно работает на локальной переменной 1. istore_1 инструкция появляется int
от стека операнда и хранилищ это в локальной переменной 1. iload_1 инструкция продвигает значение в локальной переменной 1 на стек операнда.
Использование (и повторное использование) локальных переменных является ответственностью разработчика компилятора. Специализированная загрузка и инструкции хранилища должны поощрить разработчик компилятора снова использовать локальные переменные, столько, сколько выполнимо. Получающийся код быстрее, более компактен, и использует меньше пространства во фрейме.
Определенным очень частым операциям на локальных переменных угождает особенно виртуальная машина Java. iinc инструкция постепенно увеличивает содержание локальной переменной однобайтовым значением со знаком. iinc инструкция в spin
постепенно увеличивает первую локальную переменную (ее первый операнд) 1 (ее второй операнд). iinc инструкция очень удобна, реализовывая конструкции цикличного выполнения.
for
цикл spin
выполняется, главным образом, этими инструкциями:
bipush инструкция продвигает значение 100 на стек операнда как5 iinc 1 1 // Increment local 1 by 1 (i++
) 8 iload_1 // Push local variable 1 (i
) 9 bipush 100 // Pushint
constant100
11 if_icmplt 5 // Compare and loop if less than (i
<100
)
int
, тогда if_icmplt инструкция выталкивает то значение от стека операнда и сравнивает это со мной. Если сравнение успешно выполняется (переменная i
меньше чем 100
), управление передается индексу 5 и следующей итерации for
цикл начинается. Иначе, управляйте передачами в инструкцию после if_icmplt. Если spin
пример использовал тип данных кроме int
для счетчика цикла скомпилированный код обязательно изменился бы, чтобы отразить различный тип данных. Например, если вместо int
spin
пример использует a double
, как показано,
скомпилированный кодvoid dspin() {
double i;
for (i = 0.0; i < 100.0; i++) {
; // Loop body is empty
}
}
Инструкции, которые работают на введенных данных, теперь специализируются для типаMethodvoid
dspin()
0 dconst_0 // Pushdouble
constant0.0
1 dstore_1 // Store into local variables 1 and 2 2 goto 9 // First time through don't increment 5 dload_1 // Push local variables 1 and 2 6 dconst_1 // Pushdouble
constant1.0
7 dadd // Add; there is no dinc instruction 8 dstore_1 // Store result in local variables 1 and 2 9 dload_1 // Push local variables 1 and 2 10 ldc2_w #4 // Pushdouble
constant100.0
13 dcmpg // There is no if_dcmplt instruction 14 iflt 5 // Compare and loop if less than (i
<100.0
) 17 возвратитесь //Возвратvoid
когда сделано
double
. (ldc2_w инструкция будет обсуждена позже в этой главе.) Вспомните это double
значения занимают две локальных переменные, хотя к ним только получают доступ, используя меньший индекс этих двух локальных переменных. Это также имеет место для значений типа long
. Снова например,
становитсяdouble doubleLocals(double d1, double d2) {
return d1 + d2;
}
Отметьте что локальные переменные пар локальной переменной, используемых, чтобы сохранитьMethoddouble
doubleLocals(double,double)
0 dload_1 // First argument in local variables 1 and 2 1 dload_3 // Second argument in local variables 3 and 4 2 dadd 3 dreturn
double
значения в doubleLocals
никогда не должен управляться индивидуально.Размер кода операции виртуальной машины Java 1-байтовых результатов в его скомпилированном коде, являющемся очень компактным. Однако, 1-байтовые коды операций также означают, что набор команд виртуальной машины Java должен остаться небольшим. Как компромисс, виртуальная машина Java не оказывает равную поддержку для всех типов данных: это не абсолютно ортогонально (см. Таблицу 3.2, "Поддержка типа в наборе команд виртуальной машины Java").
Например, сравнение значений типа int
в for
оператор примера spin
может быть реализован, используя единственную if_icmplt инструкцию; однако, нет никакой единственной инструкции в наборе команд виртуальной машины Java, который выполняет условный переход на значениях типа double
. Таким образом, dspin
должен реализовать его сравнение значений типа double
использование dcmpg инструкции следовало iflt инструкцией.
Виртуальная машина Java оказывает самую прямую поддержку для данных типа int
. Это частично в ожидании эффективных реализаций стеков операнда виртуальной машины Java и массивов локальной переменной. Это также мотивируется частотой int
данные в типичных программах. У других целочисленных типов есть менее прямая поддержка. Есть нет byte
, char
, или short
версии хранилища, загрузки, или добавляют инструкции, например. Вот spin
пример, записанный, используя a short
:
Это должно быть скомпилировано для виртуальной машины Java, следующим образом, используя инструкции, работающие на другом типе, наиболее вероятноvoid sspin() {
short i;
for (i = 0; i < 100; i++) {
; // Loop body is empty
}
}
int
, преобразование между short
и int
значения по мере необходимости, чтобы гарантировать, что результаты операций на short
данные остаются в пределах соответствующего диапазона: Нехватка прямой поддержкиMethodvoid
sspin()
0 iconst_0 1 istore_1 2 goto 10 5 iload_1 // Theshort
is treated as though anint
6 iconst_1 7 iadd 8 i2s // Truncateint
toshort
9 istore_1 10 iload_1 11 bipush 100 13 if_icmplt 5 16 return
byte
, char
, и short
вводит виртуальную машину Java, не является особенно болезненным, потому что значения тех типов внутренне продвигаются на int
(byte
и short
расширяются до знака на int
, char
расширяется до нуля). Операции на byte
, char
, и short
данные могут таким образом быть сделаны, используя int
инструкции. Единственная дополнительная стоимость является стоимостью усечения значений int
операции к допустимым диапазонам. long
и у типов с плавающей точкой есть промежуточный уровень поддержки в виртуальной машине Java, испытывая недостаток только в полном дополнении условных инструкций передачи управления.
align2grain
метод выравнивается int
оцените данному питанию 2: Операнды для арифметических операций выталкиваются от стека операнда, и результаты операций пододвигаются обратно на стек операнда. Результаты арифметических подвычислений могут таким образом быть сделаны доступными как операнды их вычисления вложения. Например, вычислениеint align2grain(int i, int grain) {
return ((i + grain-1) & ~(grain-1));
}
~(grain
-1)
обрабатывается этими инструкциями:
Сначала5 iload_2 // Pushgrain
6 iconst_1 // Pushint
constant1
7 isub // Subtract; push result 8 iconst_m1 // Pushint
constant -1
9 ixor // Do XOR; push result
grain
- 1
вычисляется, используя содержание локальной переменной 2 и непосредственное int
значение 1
. Эти операнды выталкиваются от стека операнда и их различия, пододвинутого обратно на стек операнда. Различие таким образом сразу доступно для использования в качестве одного операнда ixor инструкции. (Вспомните это ~x
== -1^x
.) Точно так же результат ixor инструкции становится операндом для последующей iand инструкции.
Methodint
align2grain(int,int)
0 iload_1 1 iload_2 2 iadd 3 iconst_1 4 isub 5 iload_2 6 iconst_1 7 isub 8 iconst_m1 9 ixor 10 iand 11 ireturn
int
, long
, float
, и double
, так же как ссылки на экземпляры класса String
, управляются, используя ldc, ldc_w, и ldc2_w инструкции. ldc и ldc_w инструкции привыкли к значениям доступа в пуле константы этапа выполнения (включая экземпляры класса String
) из типов кроме double
и long
. ldc_w инструкция используется вместо ldc только, когда есть большое количество элементов пула константы этапа выполнения, и больший индекс необходим, чтобы получить доступ к элементу. ldc2_w инструкция используется, чтобы получить доступ ко всем значениям типов double
и long
; нет никакой неширокой разновидности.
Интегральные константы типов byte
, char
, или short
, так же как маленький int
значения, может быть скомпилирован, используя bipush, sipush, или iconst _ <i> инструкции, как замечено ранее (§7.2). Определенные маленькие константы с плавающей точкой могут быть скомпилированы, используя fconst _ <f> и dconst _ <d> инструкции.
Во всех этих случаях компиляция является прямой. Например, константы для
устанавливаются следующим образом:void useManyNumeric() {
int i = 100;
int j = 1000000;
long l1 = 1;
long l2 = 0xffffffff;
double d = 2.2;
...do some calculations...
}
Methodvoid
useManyNumeric()
0 bipush 100 // Push a smallint
with bipush 2 istore_1 3 ldc #1 // Pushint
constant1000000
; a largerint
// value uses ldc 5 istore_2 6 lconst_1 // A tinylong
value uses short, fast lconst_1 7 lstore_3 8 ldc2_w #6 // Pushlong
0xffffffff
(that is, anint
-1
); any //long
constant value can be pushed using ldc2_w 11 lstore 5 13 ldc2_w #8 // Pushdouble
constant2.200000
; uncommon //double
values are also pushed using ldc2_w 16 dstore 7 ...do those calculations...
for
операторы показали в более раннем разделе (§7.2). Большая часть языка программирования Java другие конструкции управления (if-then-else
, do
, while
, break
, и continue
) также компилируются очевидными способами. Компиляция switch
операторы обрабатываются в отдельном участке (Раздел 7.10, "Компилируя Переключатели"), как компиляция исключений (Раздел 7.12, "Бросая и Обрабатывая Исключения") и компиляция finally
пункты (Раздел 7.13, "Компилируя наконец"). Как дальнейший пример, a while
цикл компилируется очевидным способом, хотя определенные инструкции передачи управления, сделанные доступный виртуальной машиной Java, изменяются типом данных. Как обычно есть больше поддержки данных типа int
, например:
компилируется вvoid whileInt() {
int i = 0;
while (i < 100) {
i++;
}
}
Отметьте что тестMethodvoid
whileInt()
0 iconst_0 1 istore_1 2 goto 8 5 iinc 1 1 8 iload_1 9 bipush 100 11 if_icmplt 5 14 return
while
оператор (реализованное использование if_icmplt инструкции) у основания кода виртуальной машины Java для цикла. (Это также имело место в spin
примеры ранее.) Тест, являющийся у основания цикла, вынуждает использование goto инструкции добраться до теста до первой итерации цикла. Если тот тест перестал работать, и тело цикла никогда не вводится, эта дополнительная инструкция тратится впустую. Однако, while
циклы обычно используются, когда их тело, как ожидают, будет выполнено, часто для многих итераций. Для последующих итераций, помещая тест у основания цикла экономит инструкции виртуальной машины Java каждое время вокруг цикла: если бы тест был наверху цикла, то тело цикла нуждалось бы в запаздывании goto инструкция, чтобы возвратиться к вершине.Управление создает включение других типов данных, компилируются похожими способами, но должен использовать инструкции, доступные для тех типов данных. Это приводит к несколько менее эффективному коду, потому что больше инструкций виртуальной машины Java необходимо, например:
компилируется вvoid whileDouble() {
double i = 0.0;
while (i < 100.1) {
i++;
}
}
У каждого типа с плавающей точкой есть две инструкции сравнения: fcmpl и fcmpg для типаMethodvoid
whileDouble()
0 dconst_0 1 dstore_1 2 goto 9 5 dload_1 6 dconst_1 7 dadd 8 dstore_1 9 dload_1 10 ldc2_w #4 // Pushdouble
constant100.1
13 dcmpg // To do the compare and branch we have to use... 14 iflt 5 // ...two instructions 17 return
float
, и dcmpl и dcmpg для типа double
. Разновидности отличаются только по их обработке НЭН. НЭН Неупорядочивают, таким образом, все сравнения с плавающей точкой перестали работать, если любым из их операндов является НЭН. Компилятор выбирает разновидность инструкции сравнения для соответствующего типа, который приводит к тому же самому результату, оценивают ли сбои сравнения на NON-НЭН или встречаются с НЭН.
компиляции кint lessThan100(double d) {
if (d < 100.0) {
return 1;
} else {
return -1;
}
}
ЕслиMethodint
lessThan100(double)
0 dload_1 1 ldc2_w #4 // Pushdouble
constant100.0
4 dcmpg // Push 1 ifd
is NaN ord
\>100.0
; // push 0 ifd
==100.0
5 ifge 10 // Branch on 0 or 1 8 iconst_1 9 ireturn 10 iconst_m1 11 ireturn
d
не НЭН и меньше чем 100.0
, dcmpg инструкция продвигает int
-1 на стек операнда, и ifge инструкцию не переходит. Ли d
больше чем 100.0
или НЭН, dcmpg инструкция продвигает int
1 на стек операнда, и ответвления ifge. Если d
равно 100.0
, dcmpg инструкция продвигает int
0 на стек операнда, и ответвления ifge. dcmpl инструкция достигает того же самого эффекта, если сравнение инвертируется:
становитсяint greaterThan100(double d) {
if (d > 100.0) {
return 1;
} else {
return -1;
}
}
Еще раз, ли сравнение перестало работать на значении NON-НЭН или потому что его передают НЭН, dcmpl инструкция продвигаетMethodint
greaterThan100(double)
0 dload_1 1 ldc2_w #4 // Pushdouble
constant100.0
4 dcmpl // Push -1 ifd
is Nan ord
<100.0
; //продвиньте 0 еслиd
==100.0
5 ifle 10 //Ответвление по 0 или-1 8 iconst_1 9 ireturn 10 iconst_m1 11 ireturn
int
значение на стек операнда, который заставляет ifle переходить. Если бы обе из dcmp инструкций не существовали, один из методов в качестве примера должен был бы сделать больше работы, чтобы обнаружить НЭН. компиляции кint addTwo(int i, int j) {
return i + j;
}
Условно, метод экземпляра передают aMethodint
addTwo(int,int)
0 iload_1 // Push value of local variable 1 (i
) 1 iload_2 // Push value of local variable 2 (j
) 2 iadd // Add; leaveint
result on operand stack 3 ireturn // Returnint
result
reference
к его экземпляру в локальной переменной 0. В языке программирования Java экземпляр доступен через this
ключевое слово.
Класс (static
) у методов нет экземпляра, таким образом, для них это использование нуля локальной переменной является ненужным. Метод класса начинает использовать локальные переменные в индексном нуле. Если addTwo
метод был методом класса, его параметры передадут похожим способом к первой версии:
компиляции кstatic int addTwoStatic(int i, int j) {
return i + j;
}
Единственная разница - то, что параметры метода кажутся запускающимися в локальной переменной 0, а не 1.Methodint
addTwoStatic(int,int)
0 iload_0 1 iload_1 2 iadd 3 ireturn
addTwo
метод, определенный ранее как метод экземпляра, мы могли бы записать Это компилирует вint add12and13() {
return addTwo(12, 13);
}
Вызов устанавливается первым продвижением aMethodint
add12and13()
0 aload_0 // Push local variable 0 (this
) 1 bipush 12 // Pushint
constant12
3 bipush 13 // Pushint
constant13
5 invokevirtual #4 // MethodExample.addtwo(II)I
8 ireturn // Returnint
on top of operand stack; it is // theint
result ofaddTwo()
reference
к текущему экземпляру, this
, на стек операнда. Параметры вызова метода, int
значения 12
и 13
, тогда продвигаются. Когда фрейм для addTwo
метод создается, параметры, которые передают к методу, становятся начальными значениями локальных переменных нового фрейма. Таким образом, reference
для this
и эти два параметра, продвинутые на стек операнда invoker, станут начальными значениями локальных переменных 0, 1, и 2 из вызванного метода.
Наконец, addTwo
вызывается. Когда это возвращается, int
возвращаемое значение продвигается на стек операнда фрейма invoker, add12and13
метод. Возвращаемое значение таким образом кладется на место, чтобы быть сразу возвращенным к invoker add12and13
.
Возврат из add12and13
обрабатывается ireturn инструкцией add12and13
. ireturn инструкция берет int
значение, возвращенное addTwo
, на стеке операнда текущего фрейма, и нажатиях это на стек операнда фрейма invoker. Это тогда возвращает управление invoker, делая ток фрейма invoker. Виртуальная машина Java обеспечивает отличные инструкции возврата для многих из его числовых и reference
типы данных, так же как инструкция возврата для методов без возвращаемого значения. Тот же самый набор инструкций возврата используется для всех вариантов вызовов метода.
Операнд invokevirtual инструкции (в примере, индекс пула константы этапа выполнения #4) не является смещением метода в экземпляре класса. Компилятор не знает внутреннее расположение экземпляра класса. Вместо этого это генерирует символьные ссылки на методы экземпляра, которые сохранены в пуле константы этапа выполнения. Те элементы пула константы этапа выполнения разрешаются во время выполнения, чтобы определить фактическое расположение метода. То же самое является истиной для всех других инструкций виртуальной машины Java тот класс доступа экземпляры.
Вызов addTwoStatic
, класс (static
) разновидность addTwo
, подобно, как показано:
хотя различная инструкция вызова метода виртуальной машины Java используется:int add12and13() {
return addTwoStatic(12, 13);
}
Компиляция вызова класса (Methodint add12and13()
0 bipush 12 2 bipush 13 4 invokestatic #3 // MethodExample.addTwoStatic(II)I
7 ireturn
static
) метод очень походит на компиляцию вызова метода экземпляра, кроме this
не передается invoker. Параметры метода будут таким образом получены, начинаясь с локальной переменной 0 (см. Раздел 7.6, "Получая Параметры"). invokestatic инструкция всегда используется, чтобы вызвать методы класса. invokespecial инструкция должна использоваться, чтобы вызвать методы инициализации экземпляра (см. Раздел 7.8, "Работающий с Экземплярами Класса"). Это также используется, вызывая методы в суперклассе (super
) и вызывая private
методы. Например, данный классы Near
и Far
объявленный как
методclass Near {
int it;
public int getItNear() {
return getIt();
}
private int getIt() {
return it;
}
}class Far extends Near {
int getItFar() {
return super.getItNear();
}
}
Near.getItNear
(который вызывает a private
метод), становится МетодMethodint
getItNear()
0 aload_0 1 invokespecial #5 // MethodNear.getIt()I
4 ireturn
Far.getItFar
(который вызывает метод суперкласса), становится Отметьте, что методы вызывали использование invokespecial инструкции, всегда передаютMethodint
getItFar()
0 aload_0 1 invokespecial #4 // MethodNear.getItNear()I
4 ireturn
this
к вызванному методу как его первый параметр. Как обычно это получается в локальной переменной 0. <init>
. Этот особенно именованный метод известен как метод инициализации экземпляра (§3.9). Многократные методы инициализации экземпляра, соответствуя многократным конструкторам, могут существовать для данного класса. Как только экземпляр класса был создан и его переменные экземпляра, включая таковые из класса и всех его суперклассов, был инициализирован к их значениям по умолчанию, метод инициализации экземпляра нового экземпляра класса вызывается. Например: компиляции кObject create() {
return new Object();
}
Экземпляры класса передают и возвращаются (какMethodjava.lang.Object
create()
0 new #1 // Classjava.lang.Object
3 dup 4 invokespecial #4 // Methodjava.lang.Object.<init>()V
7 areturn
reference
типы) очень как числовые значения, хотя тип reference
имеет его собственное дополнение инструкций, например:
становитсяint i; // An instance variable
MyObj example() {
MyObj o = new MyObj();
return silly(o);
}
MyObj silly(MyObj o) {
if (o != null) {
return o;
} else {
return o;
}
}
К полям экземпляра класса (переменные экземпляра) получают доступ, используя getfield и putfield инструкции. ЕслиMethodMyObj
example()
0 new #2 // ClassMyObj
3 dup 4 invokespecial #5 // MethodMyObj.<init>()V
7 astore_1 8 aload_0 9 aload_1 10 invokevirtual #4 // MethodExample.silly(LMyObj;)LMyObj;
13 areturn MethodMyObj
silly(MyObj)
0 aload_1 1 ifnull 6 4 aload_1 5 areturn 6 aload_1 7 areturn
i
переменная экземпляра типа int
, методы setIt
и getIt,
определенный как
статьvoid setIt(int value) {
i = value;
}
int getIt() {
return i;
}
Как с операндами инструкций вызова метода, операнды putfield и getfield инструкций (индекс пула константы этапа выполнения #4) не являются смещениями полей в экземпляре класса. Компилятор генерирует символьные ссылки на поля экземпляра, которые сохранены в пуле константы этапа выполнения. Те элементы пула константы этапа выполнения разрешаются во время выполнения, чтобы определить расположение поля в пределах объекта, на который ссылаются.Methodvoid
setIt(int)
0 aload_0 1 iload_1 2 putfield #4 // FieldExample.i I
5 return Methodint
getIt()
0 aload_0 1 getfield #4 // FieldExample.i I
4 ireturn
мог бы быть скомпилирован вvoid createBuffer() {
int buffer[];
int bufsz = 100;
int value = 12;
buffer = new int[bufsz];
buffer[10] = value;
value = buffer[11];
}
anewarray инструкция используется, чтобы создать одномерный массив ссылок на объект, например:Methodvoid
createBuffer()
0 bipush 100 // Pushint
constant 100 (bufsz
) 2 istore_2 // Storebufsz
in local variable 2 3 bipush 12 // Pushint
constant 12 (value
) 5 istore_3 // Storevalue
in local variable 3 6 iload_2 // Pushbufsz
... 7 newarrayint
// ...and create new array ofint
of that length 9 astore_1 // Store new array inbuffer
10 aload_1 // Pushbuffer
11 bipush 10 // Pushint
constant10
13 iload_3 // Pushvalue
14 iastore // Store value atbuffer[10]
15 aload_1 // Pushbuffer
16 bipush 11 // Pushint
constant11
18 iaload // Push value atbuffer[11]
... 19 istore_3 // ...and store it invalue
20 return
становитсяvoid createThreadArray() {
Thread threads[];
int count = 10;
threads = new Thread[count];
threads[0] = new Thread();
}
anewarray инструкция может также использоваться, чтобы создать первую размерность многомерного массива. Альтернативно, multianewarray инструкция может использоваться, чтобы создать несколько размерностей сразу. Например, трехмерный массив:Methodvoid createThreadArray()
0 bipush 10 // Pushint
constant10
2 istore_2 // Initializecount
to that 3 iload_2 // Pushcount
, used by anewarray 4 anewarray class #1 // Create new array of classThread
7 astore_1 // Store new array inthreads
8 aload_1 // Push value ofthreads
9 iconst_0 // Pushint
constant0
10 new #1 // Create instance of classThread
13 dup // Make duplicate reference... 14 invokespecial #5 // ...to pass to instance initialization method // Methodjava.lang.Thread.<init>()V
17 aastore // Store newThread
in array at0
18 return
создаетсяint[][][] create3DArray() {
int grid[][][];
grid = new int[10][5][];
return grid;
}
Первый операнд multianewarray инструкции является индексом пула константы этапа выполнения к типу класса массива, который будет создаваться. Вторым является число размерностей того типа массива, чтобы фактически создать. multianewarray инструкция может использоваться, чтобы создать все размерности типа как код дляMethodint
create3DArray()[][][]
0 bipush 10 // Pushint
10
(dimension one) 2 iconst_5 // Pushint
5
(dimension two) 3 multianewarray #1 dim #2 // Class[[[I
, a three // dimensionalint
array; // only create first two // dimensions 7 astore_1 // Store new array... 8 aload_1 // ...then prepare to return it 9 areturn
create3DArray
шоу. Отметьте, что многомерный массив является только объектом и так загружается и возвращается aload_1 и areturn инструкцией, соответственно. Для получения информации об именах классов массива см. Раздел 4.4.1. Все массивы связали длины, к которым получают доступ через arraylength инструкцию.
switch
операторы используют tableswitch и lookupswitch инструкции. tableswitch инструкция используется когда случаи switch
может быть эффективно представлен как индексы в таблицу целевых смещений. default
цель switch
используется если значение выражения switch
падения вне диапазона допустимых индексов. Например, компиляции кint chooseNear(int i) {
switch (i) {
case 0: return 0;
case 1: return 1;
case 2: return 2;
default: return -1;
}
}
tableswitch виртуальной машины Java и lookupswitch инструкции работают только наMethodint
chooseNear(int)
0 iload_1 // Push local variable 1 (argumenti
) 1 tableswitch 0 to 2: // Valid indices are 0 through 2 0: 28 // Ifi
is0
, continue at 28 1: 30 // Ifi
is1
, continue at 30 2: 32 // Ifi
is2
, continue at 32 default:34 // Otherwise, continue at 34 28 iconst_0 //i
was0
; pushint
constant0
... 29 ireturn // ...and return it 30 iconst_1 //i
was1
; pushint
constant1
... 31 ireturn // ...and return it 32 iconst_2 //i
was2
; pushint
constant2
... 33 ireturn // ...and return it 34 iconst_m1 // otherwise pushint
constant -1
... 35 ireturn // ...and return it
int
данные. Поскольку операции на byte
, char
, или short
значения внутренне продвигаются на int
, a switch
то, выражение которого оценивает к одному из тех типов, компилируется, как если бы это оценило, чтобы ввести int
. Если chooseNear
метод был записан, используя тип short
, те же самые инструкции виртуальной машины Java были бы сгенерированы, используя тип int
. Другие числовые типы должны быть сужены, чтобы ввести int
для использования в a switch
.
Где случаи switch
редки, табличное представление tableswitch инструкции становится неэффективным с точки зрения пространства. lookupswitch инструкция может использоваться вместо этого. lookupswitch пары инструкции int
ключи (значения case
метки) с целевыми смещениями в таблице. Когда lookupswitch инструкция выполняется, значение выражения switch
сравнивается с ключами в таблице. Если один из ключей соответствует значение выражения, выполнение продолжается при связанном целевом смещении. Если никакой ключ не соответствует, выполнение продолжается в default
цель. Например, скомпилированный код для
взгляды точно так же как код дляint chooseFar(int i) {
switch (i) {
case -100: return -1;
case 0: return 0;
case 100: return 1;
default: return -1;
}
}
chooseNear
, за исключением использования lookupswitch инструкции: Виртуальная машина Java определяет, что таблица lookupswitch инструкции должна быть сортирована ключом так, чтобы реализации могли использовать поиски, более эффективные чем линейное сканирование. Даже в этом случае lookupswitch инструкция должна искать свои ключи соответствие, а не просто выполнить граничную проверку и индекс в таблицу как tableswitch. Таким образом tableswitch инструкция, вероятно, более эффективна чем lookupswitch, где соображения пространства разрешают выбор.Methodint
chooseFar(int)
0 iload_1 1 lookupswitch 3: -100: 36 0: 38 100: 40 default:42 36 iconst_m1 37 ireturn 38 iconst_0 39 ireturn 40 iconst_1 41 ireturn 42 iconst_m1 43 ireturn
компилируется вpublic long nextIndex() {
return index++;
}
private long index = 0;
Отметьте, что виртуальная машина Java никогда не позволяет ее инструкциям манипулирования стеком операнда изменять или разбивать отдельные значения на стеке операнда.Methodlong nextIndex()
0 aload_0 // Pushthis
1 dup // Make a copy of it 2 getfield #4 // One of the copies ofthis
is consumed // pushinglong
fieldindex
, // above the originalthis
5 dup2_x1 // Thelong
on top of the operand stack is // inserted into the operand stack below the // originalthis
6 lconst_1 // Pushlong
constant 1 7 ladd // The index value is incremented... 8 putfield #4 // ...and the result stored back in the field 11 lreturn // The original value ofindex
is left on // top of the operand stack, ready to be returned
throw
ключевое слово. Его компиляция проста: становитсяvoid cantBeZero(int i) throws TestExc {
if (i == 0) {
throw new TestExc();
}
}
КомпиляцияMethodvoid
cantBeZero(int)
0 iload_1 // Push argument 1 (i
) 1 ifne 12 // Ifi==0
, allocate instance and throw 4 new #1 // Create instance ofTestExc
7 dup // One reference goes to the constructor 8 invokespecial #7 // MethodTestExc.<init>()V
11 athrow // Second reference is thrown 12 return // Never get here if we threwTestExc
try
-catch
конструкции являются прямыми. Например,
компилируется какvoid catchOne() {
try {
tryItOut();
} catch (TestExc e) {
handleExc(e);
}
}
Смотря более близко,Methodvoid
catchOne()
0 aload_0 // Beginning oftry
block 1 invokevirtual #6 // MethodExample.tryItOut()V
4 return // End oftry
block; normal return 5 astore_1 // Store thrown value in local variable 1 6 aload_0 // Pushthis
7 aload_1 // Push thrown value 8 invokevirtual #5 // Invoke handler method: //Example.handleExc(LTestExc;)V
11 return // Return after handlingTestExc
Exception table: From To Target Type 0 4 5 ClassTestExc
try
блок компилируется, как это было бы если try
не присутствовали: Если никакое исключение не выдается во время выполненияMethodvoid
catchOne()
0 aload_0 // Beginning oftry
block 1 invokevirtual #4 // MethodExample.tryItOut()V
4 return // End oftry
block; normal return
try
блок, это ведет себя как если бы try
не были ли: tryItOut
вызывается и catchOne
возвраты. После try
блок является кодом виртуальной машины Java, который реализует сингл catch
пункт:
Вызов5 astore_1 // Store thrown value in local variable 1 6 aload_0 // Pushthis
7 aload_1 // Push thrown value 8 invokevirtual #5 // Invoke handler method: //Example.handleExc(LTestExc;)V
11 return // Return after handlingTestExc
Exception table: From To Target Type 0 4 5 ClassTestExc
handleExc
, содержание catch
пункт, также компилируется как нормальный вызов метода. Однако, присутствие a catch
пункт заставляет компилятор генерировать запись таблицы исключений. Таблица исключений для catchOne
у метода есть одна запись, соответствующая одному параметру (экземпляр класса TestExc
) то, что catch
пункт catchOne
может обработать. Если некоторое значение, которое является экземпляром TestExc
бросается во время выполнения инструкций между индексами 0 и 4 в catchOne
, управление передается коду виртуальной машины Java по индексу 5, который реализует блок catch
пункт. Если значение, которое бросается, не является экземпляром TestExc
, catch
пункт catchOne
не может обработать это. Вместо этого значение повторно бросается в invoker catchOne
. A try
может иметь многократный catch
пункты:
Многократныйvoid catchTwo() {
try {
tryItOut();
} catch (TestExc1 e) {
handleExc(e);
} catch (TestExc2 e) {
handleExc(e);
}
}
catch
пункты данного try
оператор компилируется, просто добавляя код виртуальной машины Java для каждого catch
пункт один за другим и добавляющие записи в таблицу исключений, как показано: Если во время выполненияMethodvoid catchTwo()
0 aload_0 // Begintry
block 1 invokevirtual #5 // MethodExample.tryItOut()V
4 return // End oftry
block; normal return 5 astore_1 // Beginning of handler forTestExc1
; // Store thrown value in local variable 1 6 aload_0 // Pushthis
7 aload_1 // Push thrown value 8 invokevirtual #7 // Invoke handler method: //Example.handleExc(LTestExc1;)V
11 return // Return after handlingTestExc1
12 astore_1 // Beginning of handler forTestExc2
; // Store thrown value in local variable 1 13 aload_0 // Pushthis
14 aload_1 // Push thrown value 15 invokevirtual #7 // Invoke handler method: //Example.handleExc(LTestExc2;)V
18 return // Return after handlingTestExc2
Exception table: From To Target Type 0 4 5 ClassTestExc1
0 4 12 ClassTestExc2
try
пункт (между индексами 0 и 4) значение бросается, который соответствует параметр один или больше catch
пункты (значение является экземпляром один или больше параметров), первое (самое внутреннее) такой catch
пункт выбирается. Управление передается коду виртуальной машины Java для блока этого catch
пункт. Если брошенное значение не соответствует параметр какого-либо из catch
пункты catchTwo
, виртуальная машина Java повторно бросает значение, не вызывая код в любом catch
пункт catchTwo
. Вложенный try
-catch
операторы компилируются очень как a try
оператор с многократным catch
пункты:
становитсяvoid nestedCatch() {
try {
try {
tryItOut();
} catch (TestExc1 e) {
handleExc1(e);
}
} catch (TestExc2 e) {
handleExc2(e);
}
}
ВложениеMethodvoid nestedCatch()
0 aload_0 // Begintry
block 1 invokevirtual #8 // MethodExample.tryItOut()V
4 return // End oftry
block; normal return 5 astore_1 // Beginning of handler forTestExc1
; // Store thrown value in local variable 1 6 aload_0 // Pushthis
7 aload_1 // Push thrown value 8 invokevirtual #7 // Invoke handler method: //Example.handleExc1(LTestExc1;)V
11 return // Return after handlingTestExc1
12 astore_1 // Beginning of handler forTestExc2
; // Store thrown value in local variable 1 13 aload_0 // Pushthis
14 aload_1 // Push thrown value 15 invokevirtual #6 // Invoke handler method: //Example.handleExc2(LTestExc2;)V
18 return // Return after handlingTestExc2
Exception table: From To Target Type 0 4 5 ClassTestExc1
0 12 12 ClassTestExc2
catch
пункты представляются только в таблице исключений. Когда исключение выдается, первый (самый внутренний) пункт выгоды, который содержит сайт исключения и с соответствующим параметром, выбирается, чтобы обработать это. Например, если вызов tryItOut
(по индексу 1), бросил экземпляр TestExc1
, это было бы обработано catch
пункт, который вызывает handleExc1
. Это так даже при том, что исключение происходит в пределах границ внешнего catch
пункт (ловля TestExc2
) и даже при том, что это внешнее catch
пункт, возможно, иначе был в состоянии обработать брошенное значение. Как тонкий момент, отметьте что диапазон a catch
пункт является содержащим на "от" конца и монопольным на, чтобы закончиться (§4.7.3). Таким образом, запись таблицы исключений для catch
ловля пункта TestExc1
не покрывает инструкцию возврата при смещении 4. Однако, запись таблицы исключений для catch
ловля пункта TestExc2
действительно покрывает инструкцию возврата при смещении 11. Возвратите инструкции в пределах вложенного catch
пункты включаются в диапазон инструкций, покрытых вложением catch
пункты.
finally
try
-finally
оператор подобен тому из try-catch
. До передачи управления вне try
оператор, является ли та передача нормальной или резкой, потому что исключение было выдано, finally
пункт должен сначала быть выполнен. Для этого простого примера скомпилированный кодvoid tryFinally() {
try {
tryItOut();
} finally {
wrapItUp();
}
}
Есть четыре пути к управлению, чтобы передать за пределамиMethodvoid tryFinally()
0 aload_0 // Beginning oftry
block 1 invokevirtual #6 // MethodExample.tryItOut()V
4 jsr 14 // Callfinally
block 7 return // End oftry
block 8 astore_1 // Beginning of handler for any throw 9 jsr 14 // Callfinally
block 12 aload_1 // Push thrown value 13 athrow // ...and rethrow the value to the invoker 14 astore_2 // Beginning offinally
block 15 aload_0 // Pushthis
16 invokevirtual #5 // MethodExample.wrapItUp()V
19 ret 2 // Return fromfinally
block Exception table: From To Target Type 0 4 8 any
try
оператор: проваливаясь нижняя часть того блока, возвращаясь, выполняясь a break
или continue
оператор, или повышая исключение. Если tryItOut
возвраты, не повышая исключение, управление передается finally
блок используя jsr инструкцию. jsr 14 инструкций по индексу 4 делает "вызов подпрограммы" к коду для finally
блок по индексу 14 ( finally
блок компилируется как встроенная подпрограмма). Когда finally
блок завершается, мочение 2 управления возвратами инструкции к инструкции, следующей jsr инструкциям по индексу 4. Более подробно вызов подпрограммы работает следующим образом: jsr инструкция продвигает адрес следующих инструкций (возврат по индексу 7) на стек операнда перед переходом. astore_2 инструкция, которая является целью перехода, хранит адрес на стеке операнда в локальную переменную 2. Код для finally
блок (в этом случае aload_0 и invokevirtual инструкции) выполняется. Принятие выполнения того кода обычно завершается, мочить инструкция получает адрес от локальной переменной 2 и возобновляет выполнение в том адресе. Инструкция возврата выполняется, и tryFinally
возвраты обычно.
A try
оператор с a finally
пункт компилируется, чтобы иметь специальный обработчик исключений, тот, который может обработать любое исключение, выданное в пределах try
оператор. Если tryItOut
выдает исключение, таблицу исключений для tryFinally
ищется соответствующий обработчик исключений. Специальный обработчик находится, заставляя выполнение продолжаться по индексу 8. astore_1 инструкция по индексу 8 хранит брошенное значение в локальную переменную 1. Следующая jsr инструкция делает вызов подпрограммы к коду для finally
блок. Предполагая, что код обычно возвращается, aload_1 инструкция по индексу 12 продвигает отброшенное значение назад на стек операнда, и следующая athrow инструкция повторно бросает значение.
Компиляция a try
оператор с обоими a catch
пункт и a finally
пункт более сложен:
становитсяvoid tryCatchFinally() {
try {
tryItOut();
} catch (TestExc e) {
handleExc(e);
} finally {
wrapItUp();
}
}
ЕслиMethodvoid
tryCatchFinally()
0 aload_0 // Beginning oftry
block 1 invokevirtual #4 // MethodExample.tryItOut()V
4 goto 16 // Jump tofinally
block 7 astore_3 // Beginning of handler forTestExc
; // Store thrown value in local variable 3 8 aload_0 // Pushthis
9 aload_3 // Push thrown value 10 invokevirtual #6 // Invoke handler method: //Example.handleExc(LTestExc;)V
13 goto 16 // Huh???1 16 jsr 26 // Callfinally
block 19 return // Return after handlingTestExc
20 astore_1 // Beginning of handler for exceptions // other thanTestExc
, or exceptions // thrown while handlingTestExc
21 jsr 26 // Callfinally
block 24 aload_1 // Push thrown value... 25 athrow // ...and rethrow the value to the invoker 26 astore_2 // Beginning offinally
block 27 aload_0 // Pushthis
28 invokevirtual #5 // MethodExample.wrapItUp()V
31 ret 2 // Return fromfinally
block Exception table: From To Target Type 0 4 7 ClassTestExc
0 16 20 any
try
оператор обычно завершается, goto инструкция при индексных 4 переходах к вызову подпрограммы для finally
блок по индексу 16. finally
блок по индексу 26 выполняется, управление возвращается к инструкции возврата по индексу 19, и tryCatchFinally
возвраты обычно.
Если tryItOut
бросает экземпляр TestExc
, первый (самый внутренний) применимый обработчик исключений в таблице исключений выбирается, чтобы обработать исключение. Код для того обработчика исключений, начинающегося по индексу 7, передает брошенное значение к handleExc
и по его возврату делает тот же самый вызов подпрограммы к finally
блок по индексу 26 как в нормальном случае. Если исключение не выдается handleExc
, tryCatchFinally
возвраты обычно.
Если tryItOut
бросает значение, которое не является экземпляром TestExc
или если handleExc
непосредственно выдает исключение, условие обрабатывается второй записью в таблице исключений, которая обрабатывает любое значение, брошенное между индексами 0 и 16. Тот обработчик исключений передает управление индексу 20, где брошенное значение сначала сохранено в локальной переменной 1. Код для finally
блок по индексу 26 вызывают как подпрограмма. Если это возвращается, брошенное значение получается от локальной переменной 1 и повторно брошенное использование athrow инструкции. Если новое значение бросается во время выполнения finally
пункт, finally
аварийные прекращения работы пункта, и tryCatchFinally
возвраты резко, бросая новое значение в его invoker.
synchronized
метод. A synchronized
метод обычно не реализуется, используя monitorenter и monitorexit. Скорее это просто отличают в пуле константы этапа выполнения ACC_SYNCHRONIZED
флаг, который проверяется инструкциями вызова метода., Вызывая метод, для который ACC_SYNCHRONIZED
устанавливается, текущий поток получает монитор, вызывает метод непосредственно, и выпускает монитор, завершается ли вызов метода обычно или резко. В течение времени выполняющемуся потоку принадлежит монитор, никакой другой поток не может получить это. Если исключение выдается во время вызова synchronized
метод и synchronized
метод не обрабатывает исключение, монитор для метода автоматически выпускается прежде, чем исключение повторно бросается из synchronized
метод.
monitorenter и monitorexit инструкции существуют, чтобы поддерживать synchronized
операторы. Например:
компилируется вvoid onlyMe(Foo f) {
synchronized(f) {
doSomething();
}
}
Methodvoid onlyMe(Foo)
0 aload_1 // Pushf
1 astore_2 // Store it in local variable 2 2 aload_2 // Push local variable 2 (f
) 3 monitorenter // Enter the monitor associated withf
4 aload_0 // Holding the monitor, passthis
and... 5 invokevirtual #5 // ...callExample.doSomething()V
8 aload_2 // Push local variable 2 (f
) 9 monitorexit // Exit the monitor associated withf
10 return // Return normally 11 aload_2 // In case of any throw, end up here 12 monitorexit // Be sure to exit monitor... 13 athrow // ...then rethrow the value to the invoker Exception table: From To Target Type 4 8 11 any
Полная обработка компиляции вложенных классов и интерфейсов выходит за рамки этой главы. Однако, заинтересованные читатели могут обратиться к Внутренней Спецификации Классов в
.
javac
компилятор выпуска 1.0.2 JDK Sun.
Содержание | Предыдущий | Следующий | Индекс
Спецификация Виртуальной машины JavaTM
Авторское право © Sun Microsystems, Inc 1999 года. Все права защищены
Пожалуйста, отправьте любые комментарии или исправления к jvm@java.sun.com