Spec-Zone .ru
спецификации, руководства, описания, API
|
Содержание | Предыдущий | Следующий | Индекс | Спецификация Виртуальной машины JavaTM |
ГЛАВА 7
Виртуальная машина Java разрабатывается, чтобы поддерживать язык программирования Java. JDK Sun 1.0.2 выпуска языка программирования Java содержит обоих компилятор от исходного кода Java до набора команд виртуальной машины Java (javac
) и система времени выполнения, которая реализует виртуальную машину Java непосредственно (java
). Понимание, как один компилятор Java использует виртуальную машину Java, полезно для возможного разработчика компилятора Java, так же как для одной попытки понять работу виртуальной машины Java.
Хотя эта глава концентрируется на компиляции кода Java, виртуальная машина Java не предполагает, что инструкции, которые это выполняет, были сгенерированы из исходного кода Java. В то время как было много усилий, нацеленных на компиляцию других языков к виртуальной машине Java, версия 1.0.2 виртуальной машины Java не была разработана, чтобы поддерживать широкий диапазон языков. Некоторые языки могут быть размещены справедливо непосредственно виртуальной машиной Java. Другие могут поддерживать конструкции, которые только могут быть реализованы неэффективно.
Мы полагаем, что ограниченные продолжения к будущим версиям виртуальной машины Java поддерживают более широкий диапазон языков более непосредственно. Пожалуйста, свяжитесь с нами в jvm@javasoft.com
если у Вас есть интерес к этому усилию.
Отметьте, что термин "компилятор" иногда используется, обращаясь к транслятору от набора команд виртуальной машины Java к набору команд определенного ЦП. Один пример такого транслятора "Как раз вовремя" (JIT) генератор кода, который генерирует специфичные для платформы инструкции только после того, как код виртуальной машины Java был загружен в виртуальную машину Java. Эта глава не решает проблемы, связанные с генерацией кода, только связанные с компиляцией от исходного кода Java до инструкций виртуальной машины Java.
javac
компилятор в JDK Sun 1.0.2 выпуска генерирует для примеров. Код виртуальной машины Java пишется на неофициальном "ассемблере виртуальной машины", выведенном Sun javap
утилита, также распределенная с JDK. Можно использовать javap
генерировать дополнительные примеры скомпилированных методов Java. Формат примеров должен быть знакомым любому, кто считал код блока. Каждая инструкция принимает форму
<Индекс> индекс кода операции инструкции в массиве, который содержит байты кода виртуальной машины Java для этого метода. Альтернативно, <индекс> может считаться байтовым смещением с начала метода. <Код операции> мнемосхема для кода операции инструкции, и нуль, или больше <operandN> является операндами инструкции. Дополнительное <комментарий> дается в конце стиля Java - строки комментируют синтаксис:
<index> <opcode> [<operand1> [<operand2>...]] [<comment>]
8 | bipush 100 | //Продвиньте постоянный 100
|
Часть материала в комментариях испускается javap
; остальное предоставляется авторами. <Индекс>, снабжающий каждую инструкцию предисловием, может использоваться в качестве цели инструкции передачи управления. Например, goto 8 передач инструкции управляет к инструкции по индексу 8. Отметьте, что фактические операнды инструкций передачи управления виртуальной машиной Java являются смещениями от адресов кодов операций тех инструкций; эти операнды выводятся на экран javap
, и показываются в этой главе, как более легко смещения чтения в их методы.
Мы снабжаем предисловием операнд, представляющий постоянный индекс пула со знаком хеша, и следуем инструкциям комментарием, идентифицирующим постоянный элемент пула, на который ссылаются, как в
10 | ldc #1 | //Плавание 100. 000000
|
9 | invokevirtual #4 | //Пример метода.addTwo(II)I
|
В целях этой главы мы не волнуемся об определении деталей, таких как размеры операнда.
spin
метод просто вращается вокруг пустого for
цикл 100 раз:
void spin() {
Компиляции компилятора Javaint i;
for (i = 0; i < 100; i++) { ;
// Loop body is empty
}
}
spin
к
Метод void
spin()
0 iconst_0
// Push int
constant 0
1 istore_1
// Store into local 1 ( i
=0
)
2 goto 8
// First time through don't increment
5 iinc 1 1
// Increment local 1 by 1 ( i++
)
8 iload_1
// Push local 1 ( i
)
9 bipush 100
// Push int
constant (100
)
11 if_icmplt 5
// Compare, loop if < ( i
< 100
)
14 return
//Возвратиться void
когда сделано
Виртуальная машина Java со стековой организацией с большинством операций, берущих один или более операндов от стека операнда текущего фрейма виртуальной машины Java, или продвигающих результаты назад на стек операнда. Новый фрейм создается каждый раз, когда метод Java вызывается, и с этим создается новый стек операнда и набор локальных переменных для использования тем методом (см. Раздел 3.6, "Фреймы"). В любой точке вычисления, таким образом, вероятно, будет много фреймов и одинаково много стеков операнда на поток управления, соответствуя многим вложенным вызовам метода. Только стек операнда в текущем фрейме является активным.
Набор команд виртуальной машины 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.
Определенным очень частым операциям на локальных переменных угождает особенно виртуальная машина Java. iinc инструкция постепенно увеличивает содержание локальной переменной однобайтовым значением со знаком. iinc инструкция в spin
постепенно увеличивает первую локальную переменную (ее первый операнд) 1 (ее второй операнд). iinc инструкция очень удобна, реализовывая конструкции цикличного выполнения.
for
цикл spin
выполняется, главным образом, этими инструкциями:
5 | iinc 1 1 | //Постепенно увеличьте локальный 1 1 (я ++) |
8 | iload_1 | //Продвиньте локальный 1 (i) |
9 | bipush 100 | //Продвиньте международную константу (100) |
11 | if_icmplt 5 | //Сравнитесь, цикл если <(я <100) |
bipush инструкция продвигает значение 100 на стек операнда как int
, тогда if_icmplt инструкция выталкивает то значение от стека и сравнивает это со мной. Если сравнение успешно выполняется (переменная Java 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
}
}
Метод void
dspin()
0 dconst_0 // Push double constant 0.0
1 dstore_1 // Store into locals 1 and 2 (i = 0.0)
2 goto 9 // First time through don't increment
5 dload_1 // Push double onto operand stack
6 dconst_1 // Push double constant 1 onto stack
7 dadd // Add; there is no dinc instruction
8 dstore_1 // Store result in locals 1 and 2
9 dload_1 // Push local
10 ldc2_w #4 // Double 100.000000
13 dcmpg // There is no if_dcmplt instruction
14 iflt 5 // Compare, loop if < (i < 100.000000)
17 return // Return void when done
Инструкции, которые работают на введенных данных, теперь специализируются для типа double
. (ldc2_w инструкция будет обсуждена позже в этой главе.)
Отметьте это в dspin
, double
значения используют два слова хранения, ли на стеке операнда или в локальных переменных. Это также имеет место для значений типа long
. Как другой пример:
double doubleLocals(double d1, double d2) {
становитсяreturn d1 + d2;
}
Метод double
doubleLocals(double,double)
0 dload_1 // First argument in locals 1 and 2
1 dload_3 // Second argument in locals 3 and 4
2 dadd // Each also uses two words on stack
3 dreturn
Всегда необходимо получить доступ к словам типа с двумя словами в парах и в их первоначальном заказе. Например, слова double
значения в doubleLocals
никогда не должен управляться индивидуально.
Размер кода операции виртуальной машины Java однобайтовых результатов в его скомпилированном коде, являющемся очень компактным. Однако, однобайтовые коды операций также означают, что набор команд виртуальной машины Java должен остаться небольшим. Как компромисс, виртуальная машина Java не оказывает равную поддержку для всех типов данных: это не абсолютно ортогонально (см. Таблицу 3.1, "Поддержка типа в наборе команд виртуальной машины Java"). В случае dspin
, отметьте, что нет никакой if_dcmplt инструкции в наборе команд виртуальной машины Java. Вместо этого сравнение должно быть выполнено, используя dcmpg, сопровождаемый iflt, требуя еще одной инструкции виртуальной машины Java чем int
версия spin
.
Виртуальная машина Java оказывает самую прямую поддержку для данных типа int
. Это частично, потому что стек операнда виртуальной машины Java и локальные переменные являются одним широким словом, и слово, как гарантируют, будет содержать значения всех целочисленных типов до и включая int
значение. Это также мотивируется частотой int
данные в типичных программах Java.
У меньших целочисленных типов есть менее прямая поддержка. Есть нет byte
, char
, или short
версии хранилища, загрузки, или добавляют инструкции, например. Вот spin
пример, записанный, используя a short
:
void sspin() {
short i;
for (i = 0; i < 100; i++) {
; // Loop body is empty
}
}
Это должно быть скомпилировано для инструкций использования виртуальной машины Java, работающих на другом типе, наиболее вероятно int
, преобразование между short
и int
значения по мере необходимости, чтобы гарантировать, что результаты операций на short
данные остаются в пределах соответствующего диапазона:
метод void
sspin()
0 iconst_0
1 istore_1
2 goto 10
5 iload_1 // The short
is stored in an int
6 iconst_1
7 iadd
8 i2s // Truncate int
to short
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 | //Загрузка grain на стек операнда
|
6 | iconst_1 | //Постоянная загрузка 1 на стек операнда
|
7 | isub | //Вычтите; продвиньте результат на стек |
8 | iconst_m1 | //Постоянная загрузка -1 на стек операнда
|
9 | ixor | //Сделайте XOR; продвиньте результат на стек |
Сначала grain
-1
вычисляется, используя содержание локальной переменной 2 и непосредственное int
значение 1
. Эти операнды выталкиваются от стека операнда и их различия, пододвинутого обратно на стек операнда, где это сразу доступно для использования в качестве одного операнда ixor инструкции (вспомните это ~x
== -1^x
). Точно так же результат ixor инструкции становится операндом для последующей iand инструкции.
Метод int
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
(тегируются постоянные элементы пула CONSTANT_String
), управляется, используя ldc, ldc_w, и ldc2_w инструкции. ldc и ldc_w инструкции привыкли к значениям одного слова доступа в постоянном пуле (включая экземпляры класса String
), и ldc2_w используется, чтобы получить доступ к значениям с двумя словами. ldc_w инструкция используется вместо ldc только, когда есть большое количество постоянных элементов пула, и больший индекс необходим, чтобы получить доступ к элементу. ldc2_w инструкция используется, чтобы получить доступ ко всем элементам с двумя словами; нет никакой неширокой разновидности.
Интегральные константы типов 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...
}
Метод void
useManyNumeric()
0 bipush 100 // Push a small int
with bipush
2 istore_1
3 ldc #1 // Integer 1000000
; a larger int
// value uses ldc
5 istore_2
6 lconst_1 // A tiny long
value uses short, fast lconst_1
7 lstore_3
8 ldc2_w #6 // A long
0xffffffff
(that is, an int
-1
); any
// long
constant value can be pushed by ldc2_w
11 lstore 5
13 ldc2_w #8 // Double 2.200000
; so do
// uncommon double
values
16 dstore 7
...do those calculations...
for
оператор показали в более раннем разделе (§7.2). Большая часть другого внутриметода Java управляет конструкциями передачи (if-then-else
, do
, while
, break
, и continue
) также компилируются очевидными способами. Компиляция Java switch
оператор обрабатывается в отдельном участке (Раздел 7.10, "Компилируя Переключатели"), как компиляция исключений (Раздел 7.12, "Бросая и Обрабатывая Исключения") и Java finally
оператор (Раздел 7.13, "Компилируя наконец"). Как дальнейший пример, a while
цикл компилируется очевидным способом, хотя определенные инструкции передачи управления, сделанные доступный виртуальной машиной Java, изменяются типом данных. Как обычно есть больше поддержки данных типа int
:
void whileInt() {
компилируется вint i = 0;
while (i < 100) {
i++;
}
}
Метод void
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++;
}
}
Метод void
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 // Double 100.100000
13 dcmpg // To test we have to use
14 iflt 5 // two instructions...
17 return
У каждого типа с плавающей точкой есть две инструкции сравнения: fcmpl и fcmpg для типа float
, и dcmpl и dcmpg для типа double
. Разновидности отличаются только по их обработке НЭН. НЭН Неупорядочивают, таким образом, все сравнения с плавающей точкой перестали работать, если любым из их операндов является НЭН. Компилятор выбирает разновидность инструкции сравнения для соответствующего типа, который приводит к тому же самому результату, оценивают ли сбои сравнения на NON-НЭН или встречаются с НЭН. Например:
int lessThan100(double d) {
компиляции кif (d < 100.0) {
return 1;
} else {
return -1;
}
}
Метод int
lessThan100(double)
0 dload_1
1 ldc2_w #4 // Double 100.000000
4 dcmpg // Push 1 if d
is NaN or d
> 100.000000
;
// push 0 if d
== 100.000000
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;
}
}
Метод int
greaterThan100(double)
0 dload_1
1 ldc2_w #4 // Double 100.000000
4 dcmpl // Push -1 if d
is Nan or d
< 100.000000
;
//продвиньте 0 если d
== 100.000000
5 ifle 10 //Ответвление по 0 или-1
8 iconst_1
9 ireturn
10 iconst_m1
11 ireturn
Еще раз, ли сравнение перестало работать на значении NON-НЭН или потому что его передают НЭН, dcmpl инструкция продвигает int
значение на стек операнда, который заставляет ifle переходить. Если бы обе из dcmp инструкций не существовали, один из методов в качестве примера должен был бы сделать больше работы, чтобы обнаружить НЭН.
int addTwo(int i, int j) {
компиляции кreturn i + j;
}
Метод int
addTwo(int,int)
0 iload_1 // Push value of local 1 ( i
)
1 iload_2 // Push value of local 2 ( j
)
2 iadd // Add; leave int
result on val stack
3 ireturn // Return int
result
Условно, метод экземпляра передают a reference
к его экземпляру в нуле локальной переменной. Экземпляр доступен в Java через this
ключевое слово. Код, чтобы продвинуть this
в локальную переменную нуль должен присутствовать в invoker метода экземпляра (см. Раздел 7.7, "Вызывая Методы").
Класс (static
) у методов нет экземпляра, таким образом, для них это использование нуля локальной переменной является ненужным. Метод класса начинает использовать локальные переменные в индексном нуле. Если addTwo
метод был методом класса, его параметры передадут похожим способом к первой версии:
static int addTwoStatic(int i, int j) {
компиляции кreturn i + j;
}
Метод int
addTwoStatic(int,int)
0 iload_0
1 iload_1
2 iadd
3 ireturn
Единственная разница - то, что параметры метода кажутся запускающимися в локальной переменной 0, а не 1.
addTwo
метод, определенный ранее как метод экземпляра, мы могли бы записать
int add12and13() {
Это компилирует вreturn addTwo(12, 13);
}
0 | aload_0 | //Продвиньте эту локальную переменную 0 (this ) на стек
|
1 | bipush 12 | //Продвинуть int постоянный 12 на стек
|
3 | bipush 13 | //Продвинуть int постоянный 13 на стек
|
5 | invokevirtual #4 | //Метод Example.addtwo(II)I
|
8 | ireturn | //Возвратиться int сверху стека; это
|
|
| // int результат addTwo()
|
Вызов устанавливается первым продвижением a 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 не знает внутреннее расположение экземпляра класса. Вместо этого это генерирует символьные ссылки на методы экземпляра, которые сохранены в постоянном пуле. Те постоянные элементы пула разрешаются во время выполнения, чтобы определить фактическое расположение метода. То же самое является истиной для всех других инструкций виртуальной машины Java тот класс доступа экземпляры.
Вызов addTwoStatic
, класс (static
) разновидность addTwo
, подобно:
int add12and13() {
хотя различная инструкция вызова метода виртуальной машины Java используется:return addTwoStatic(12, 13);
}
Метод int add12and13()
0 bipush 12
2 bipush 13
4 invokestatic #3 // Method Example.addTwoStatic(II)I
7 ireturn
Компиляция вызова класса (static
) метод очень походит на компиляцию вызова метода экземпляра, кроме this
не передается invoker. Параметры метода будут таким образом получены, начинаясь с локальной переменной 0 (см. Раздел 7.6, "Получая Параметры"). invokestatic инструкция всегда используется, чтобы вызвать методы класса.
invokespecial инструкция должна использоваться, чтобы вызвать инициализацию экземпляра (<init>
) методы (см. Раздел 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
метод), становится
Метод int
getItNear()
0 aload_0
1 invokespecial #5 // Method Near.getIt()I
4 ireturn
Метод Far.getItFar
(который вызывает метод суперкласса), становится
Метод int
getItFar()
0 aload_0
1 invokespecial #4 // Method Near.getItNear()I
4 ireturn
Отметьте, что методы вызывали использование invokespecial инструкции, всегда передают
this
к вызванному методу как его первый параметр. Как обычно это получается в локальной переменной 0. <init>
) вызывается. [Вспомните, что на уровне виртуальной машины Java, конструктор появляется как метод со специальным предоставленным компилятор именем <init>
. Этот специальный метод известен как метод инициализации экземпляра (§3.8). Многократные методы инициализации экземпляра, соответствуя многократным конструкторам, могут существовать для данного класса.] Например:
Object create() {
компиляции кreturn new Object();
}
Метод java.lang.Object
create()
0 new #1 // Class java.lang.Object
3 dup
4 invokespecial #4 // Method java.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;
}
}
Метод MyObj
example()
0 new #2 // Class MyObj
3 dup
4 invokespecial #5 // Method MyObj.<init>()V
7 astore_1
8 aload_0
9 aload_1
10 invokevirtual #4
// Method Example.silly(LMyObj;)LMyObj;
13 areturn
Метод
MyObj
silly(MyObj)
0 aload_1
1 ifnull 6
4 aload_1
5 areturn
6 aload_1
7
areturn
К полям экземпляра класса (переменные экземпляра) получают доступ, используя getfield и putfield инструкции. Если i
переменная экземпляра типа int
, методы setIt
и getIt,
определенный как
void setIt(int value) {
статьi = value;
}
int getIt() {
return i;
}
Метод void
setIt(int)
0 aload_0
1 iload_1
2 putfield #4 // Field Example.i I
5 return
Как с операндами инструкций вызова метода, операнды putfield и getfield инструкций (постоянный индекс пула #4) не являются смещениями полей в экземпляре класса. Компилятор Java генерирует символьные ссылки на поля экземпляра, которые сохранены в постоянном пуле. Те постоянные элементы пула разрешаются во время выполнения, чтобы определить фактическое полевое смещение.
Methodint
getIt()
0 aload_0 1 getfield #4 // Field Example.i I 4 ireturn
void createBuffer() {
мог бы быть скомпилирован вint buffer[];
int bufsz = 100;
int value = 12;
buffer = new int[bufsz];
buffer[10] = value;
value = buffer[11];
}
Метод void
createBuffer()
0 bipush 100 // Push bufsz
2 istore_2 // Store bufsz in local 2
3 bipush 12 // Push value
5 istore_3 // Store value in local 3
6 iload_2 // Push bufsz...
7 newarray int
// ...and create new array of int
9 astore_1 // Store new array in buffer
10 aload_1 // Push buffer
11 bipush 10 // Push constant 10
13 iload_3 // Push value
14 iastore // Store value at buffer[10]
15 aload_1 // Push buffer
16 bipush 11 // Push constant 11
18 iaload // Push value at buffer[11]
19 istore_3 // ...and store it in value
20 return
anewarray инструкция используется, чтобы создать одномерный массив ссылок на объект:
void createThreadArray() {
становитсяThread threads[];
int count = 10;
threads = new Thread[count];
threads[0] = new Thread();
}
Метод void createThreadArray()
0 bipush 10 // Push 10...
2 istore_2 // ...and initialize count to that
3 iload_2 // Push count, used by anewarray
4 anewarray class #1 // Create new array of class Thread
7 astore_1 // Store new array in threads
8 aload_1 // Load value of threads on stack
9 iconst_0 // Load 0 into stack
10 new #1 // Create instance of class Thread
13 dup // Make duplicate reference...
14 invokespecial #5 // ...to pass to initialization method
// Method java.lang.Thread.<init>()V
17 aastore // Store new Thread in array at 0
18 return
anewarray инструкция может также использоваться, чтобы создать первую размерность многомерного массива. Альтернативно, multianewarray инструкция может использоваться, чтобы создать несколько размерностей сразу. Например, трехмерный массив в следующем:
int[][][] create3DArray() {
создаетсяint grid[][][];
grid = new int[10][5][];
return grid;
}
Метод int
create3DArray()[][][]
0 bipush 10 // Push 10 (dimension one)
2 iconst_5 // Push 5 (dimension two)
3 multianewarra y #1 dim #2 // Class [[[I, a three
// dimensional int array;
// only create first two
// dimensions
7 astore_1 // Store new array...
8 aload_1 // ...then prepare to return it
9 areturn
Первый операнд multianewarray инструкции является постоянным индексом пула к типу класса массива, который будет создаваться. Вторым является число размерностей того типа массива, чтобы фактически создать. multianewarray инструкция может использоваться, чтобы создать все размерности типа как код для create3DArray
шоу. Отметьте, что многомерный массив является только объектом, и так загружается и возвращается aload_1 и areturn инструкцией, соответственно. Для получения информации об именах классов массива см. §4.4.1.
Все массивы связали длины, к которым получают доступ через arraylength инструкцию.
switch
операторы компилируются, используя tableswitch и lookupswitch инструкции. tableswitch инструкция используется когда случаи switch
может быть эффективно представлен как индексы в таблицу целевых смещений. default
цель switch
используется если значение выражения switch
падения вне диапазона допустимых индексов. Например,
int chooseNear(int i) {int chooseNear(int i) {
компиляции кswitch (i) {
case 0: return 0;
case 1: return 1;
case 2: return 2;
default: return -1;
}
}
Метод int
chooseNear(int)
0 iload_1 // Load local 1 (argument i)
1 tableswitch 0 to 2: // Valid indices are 0 through 2
0: 28 // If i is 0, continue at 28
1: 30 // If i is 1, continue at 30
2: 32 // If i is 2, continue at 32
default:34 // Otherwise, continue at 34
28 iconst_0 // i was 0; push int 0...
29 ireturn // ...and return it
30 iconst_1 // i was 1; push int 1...
31 ireturn // ...and return it
32 iconst_2 // i was 2; push int 2...
33 ireturn // ...and return it
34 iconst_m1 // otherwise push int -1...
35 ireturn // ...and return it
tableswitch виртуальной машины Java и lookupswitch инструкции только работают на 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 инструкции:
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 | const_m1 | |
43 | ireturn |
Виртуальная машина Java определяет, что таблица lookupswitch инструкции должна быть сортирована ключом так, чтобы реализации могли использовать поиски, более эффективные чем линейное сканирование. Даже в этом случае lookupswitch инструкция должна искать свои ключи соответствие, а не просто выполнить граничную проверку и индекс в таблицу как tableswitch. Таким образом tableswitch инструкция, вероятно, более эффективна чем lookupswitch, где соображения пространства разрешают выбор.
public long nextIndex() {
компилируется вreturn index++;
}
private long index = 0;
Метод long nextIndex()
0 aload_0 // Write this onto operand stack
1 dup // Make a copy of it
2 getfield #4 // One of the copies of this is consumed
// loading long field index onto stack,
// above the original this
5 dup2_x1 // The long on top of the stack is
// inserted into the stack below the
// original this
6 lconst_1 // A long 1 is loaded onto the stack
7 ladd // The index value is incremented
8 putfield #4 // and the result stored back in the field
11 lreturn // The original value of index is left on
// top of the stack, ready to be returned
Отметьте, что виртуальная машина Java никогда не позволяет ее инструкциям манипулирования стеком операнда изменять или перемещать слова ее типов данных с двумя словами индивидуально.
throw
ключевое слово. Его компиляция проста:
void cantBeZero(int i) throws TestExc {
становитсяif (i == 0) {
throw new TestExc();
}
}
Метод void
cantBeZero(int)
0 iload_1 // Load argument 1 (i) onto stack
1 ifne 12 // If i==0, allocate instance and throw
4 new #1 // Create instance of TestExc
7 dup // One reference goes to the constructor
8 invokespecial #7 // Method TestExc.<init>()V
11 athrow // Second reference is thrown
12 return // Never get here if we threw TestExc
Компиляция Java try
-catch
является прямым. Например:
void catchOne() {
компилируется какtry {
tryItOut();
} catch (TestExc e) {
handleExc(e);
}
}
Метод void
catchOne()
0 aload_0 // Beginning of try
block
1 invokevirtual #6 // Method Example.tryItOut()V
4 return // End of try
block; normal return
5 astore_1 // Store thrown value in local variable 1
6 aload_0 // Load this
onto stack
7 aload_1 // Load thrown value onto stack
8 invokevirtual #5 // Invoke handler method:
// Example.handleExc(LTestExc;)V
11 return // Return after handling TestExc
От | К | Цель | Ввести |
0 | 4 | 5 |
Класс TestExc
|
Смотря более близко, try
блок компилируется, как это было бы если try
не присутствовали:
Метод void
catchOne()
0 aload_0 // Beginning of try
block
1 invokevirtual #4 // Method Example.tryItOut()V
4 return // End of try
block; normal return
Если никакое исключение не выдается во время выполнения try
блок, это ведет себя как если бы try
не были ли: tryItOut
вызывается и catchOne
возвраты.
После try
блок является кодом виртуальной машины Java, который реализует сингл catch
пункт:
5 | astore_1 | //Сохраните брошенное значение в локальной переменной 1 |
6 | aload_0 | //Загрузка this на стек
|
7 | aload_1 | //Загрузите брошенное значение на стек |
8 | invokevirtual #5 | //Вызовите метод обработчика: |
|
| // Example.handleExc(LTestExc;)V
|
11 | возвратиться | //Возвратитесь после обработки TestExc
|
Exception table:
From | To | Target | Type |
0 | 4 | 5 |
Class TestExc
|
handleExc
, the contents of the catch
clause, is also compiled like a
normal method invocation. However, the presence of a catch
clause causes the compiler to generate an exception table entry. The exception table for the catchOne
method
has one entry corresponding to the one argument (an instance of class TestExc
) that the
catch
clause of catchOne
can handle. If some value that is an instance of TestExc
is
thrown during execution of the instructions between in-dices 0 and 4 (inclusive) in
catchOne
, control is transferred to the Java Virtual Machine code at index 5, which
implements the block of the catch
clause. If the value that is thrown is not an instance
of TestExc
, the catch
clause of catchOne
cannot handle it. Instead, the value is
rethrown to the invoker of catchOne
.
A try
может иметь многократный catch
пункты:
void catchTwo() {void catchTwo() {
Многократныйtry {
tryItOut();
} catch (TestExc1 e) {
handleExc(e);
} catch (TestExc2 e) {
handleExc(e);
}
catch
пункты данного try
оператор компилируется, просто добавляя код виртуальной машины Java для каждого catch
пункт один за другим, и добавляющие записи в таблицу исключений:
Метод void catchTwo()}
0 aload_0 // Begin try
block
1 invokevirtual #5 // Method Example.tryItOut()V
4 return // End of try
block; normal return
5 astore_1 // Beginning of handler for TestExc1
;
// Store thrown value in local variable 1
6 aload_0 // Load this
onto stack
7 aload_1 // Load thrown value onto stack
8 invokevirtual #7 // Invoke handler method:
// Example.handleExc(LTestExc1;)V
11 return // Return after handling TestExc1
12 astore_1 // Beginning of handler for TestExc2
;
// Store thrown value in local variable 1
13 aload_0 // Load this
onto stack
14 aload_1 // Load thrown value onto stack
15 invokevirtual #7 // Invoke handler method:
// Example.handleExc(LTestExc2;)V
18 return // Return after handling TestExc2
Exception table:
From | To | Target | Type |
0 | 4
| 5
| Class TestExc1
|
0 | 4
| 12
| Class TestExc2
|
try
пункт (между индексами 0 и 4) значение бросается, который соответствует параметр один или больше catch
блоки (значение является экземпляром один или больше параметров), первое (крайнее левое) такой catch
пункт выбирается. Управление передается коду виртуальной машины Java для блока этого catch
пункт. Если брошенное значение не соответствует параметр какого-либо из catch
пункты catchTwo
, виртуальная машина Java повторно бросает значение, не вызывая код в любом catch
пункт catchTwo
. Вложенный try
-catch
операторы компилируются очень как a try
оператор с многократным catch
пункты:
void nestedCatch() {
void nestedCatch() {
становитсяtry {
try {
tryItOut();
} catch (TestExc1 e) {
handleExc1(e);
}
} catch (TestExc2 e) {
handleExc2(e);
}
}
Метод void nestedCatch()
0 aload_0 // Begin try
block
1 invokevirtual #8 // Method Example.tryItOut()V
4 return // End of try
block; normal return
5 astore_1 // Beginning of handler for TestExc1
;
// Store thrown value in local variable 1
6 aload_0 // Load this
onto stack
7 aload_1 // Load thrown value onto stack
8 invokevirtual #7 // Invoke handler method:
// Example.handleExc1(LTestExc1;)V
11 return // Return after handling TestExc1
12 astore_1 // Beginning of handler for TestExc2
;
// Store thrown value in local variable 1
13 aload_0 // Load this
onto stack
14 aload_1 // Load thrown value onto stack
15 invokevirtual #6 // Invoke handler method:
// Example.handleExc2(LTestExc2;)V
18 return // Return after handling TestExc2
От | К | Цель | Ввести |
0 | 4 | 5 | Класс TestExc1 |
0 | 12 | 12 | Класс TestExc2 |
Вложение catch
пункты представляются только в таблице исключений. Когда исключение выдается, самый внутренний пункт выгоды, который содержит сайт исключения и с соответствующим параметром, выбирается, чтобы обработать это. Например, если вызов tryItOut
(по индексу 1), бросил экземпляр TestExc1
, это было бы обработано catch
пункт, который вызывает handleExc1
. Это так даже при том, что исключение происходит в пределах границ внешнего catch
пункт (ловля TestExc2
), и даже при том, что это внешнее catch
пункт, возможно, иначе был в состоянии обработать брошенное значение.
Как тонкий момент, отметьте что диапазон a catch
пункт является содержащим на "от" конца и монопольным на, чтобы закончиться (см. §4.7.4). Таким образом, запись таблицы исключений для catch
ловля пункта TestExc1
не покрывает инструкцию возврата при смещении 4. Однако, запись таблицы исключений для catch
ловля пункта TestExc2
действительно покрывает инструкцию возврата при смещении 11. Возвратите инструкции в пределах вложенного catch
пункты включаются в диапазон инструкций, покрытых вложением catch
пункты.
try
-finally
оператор подобен тому из try-catch
. До передачи управления вне try
оператор, является ли та передача нормальной или резкой, потому что исключение было выдано, finally
пункт должен сначала быть выполнен. Для простого примера:
void tryFinally() {
try {
tryItOut();
} finally {
wrapItUp();
}
}
Метод void tryFinally()
0 aload_0 // Beginning of try
block
1 invokevirtual #6 // Method Example.tryItOut()V
4 jsr 14 // Call finally
block
7 return // End of try
block
8 astore_1 // Beginning of handler for any throw
9 jsr 14 // Call finally
block
12 aload_1 // Push thrown value,
13 athrow // and rethrow the value to the invoker
14 astore_2 // Beginning of finally
block
15 aload_0 // Push this
onto stack
16 invokevirtual #5 // Method Example.wrapItUp()V
19 ret 2 // Return from finally
block
От | К | Цель | Ввести |
0 | 4 | 8 | любой |
Есть четыре пути к управлению, чтобы передать за пределами 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();
}
}
Method
void
tryCatchFinally()
0
aload_0
// Beginning of try
block1
invokevirtual #4
// Method Example.tryItOut()V
4
goto 16
// Jump to finally
block7
astore_3
// Beginning of handler for TestExc
;
// Store thrown value in local variable 3 8
aload_0
// Push this
onto stack9
aload_3
// Push thrown value onto stack 10
invokevirtual #6
// Invoke handler method:
// Example.handleExc(LTestExc;)V
13
goto 16
// Huh???1 16
jsr 26
// Call finally
block19
return
// Return after handling TestExc
20
astore_1
// Beginning of handler for exceptions
// other than TestExc
, or exceptions
// thrown while handling TestExc
21
jsr 26
// Call finally
block24
aload_1
// Push thrown value, 25
athrow
// and rethrow the value to the invoker 26
astore_2
// Beginning of finally
block27
aload_0
// Push this
onto stack28
invokevirtual #5
// Method Example.wrapItUp()V
31 ret 2 // Return from finally
block
Exception table:
From | To | Target | Type |
0 | 4 | 7 |
Class TestExc
|
0 | 16 | 20 | any |
try
statement completes normally, the goto instruction at index 4 jumps to the subroutine call for the finally
block at index 16. The finally
block at index 26 is executed, control returns to the return instruction at index 19, and tryCatchFinally
returns normally.
If tryItOut
throws an instance of TestExc
, the first (innermost) applicable exception handler in the exception table is chosen to handle the exception. The code for that exception handler, beginning at index 7, passes the thrown value to handleExc
, and on its return makes the same subroutine call to the finally
block at index 26 as in the normal case. If an exception is not thrown by handleExc
, tryCatchFinally
returns normally.
If tryItOut
throws a value that is not an instance of TestExc
, or if handleExc
itself throws an exception, the condition is handled by the second entry in the exception table, which handles any value thrown between indices 0 and 16. That exception handler transfers control to index 20, where the thrown value is first stored in local variable 1. The code for the finally
block at index 26 is called as a subroutine. If it returns, the thrown value is retrieved from local variable 1 and rethrown using the athrow instruction. If a new value is thrown during execution of the finally
clause, the finally
clause aborts and tryCatchFinally
returns abnormally, throwing the new value to its invoker.
synchronized
method.
A synchronized
method is not normally implemented using monitorenter and monitorexit. Rather, it is simply distinguished in the constant pool by the ACC_SYNCHRONIZED
flag, which is checked by the method invocation instructions. When invoking a method for which ACC_SYNCHRONIZED
is set, the current thread acquires a monitor, invokes the method itself, and releases the monitor whether the method invocation completes normally or abruptly. During the time the executing thread owns the monitor, no other thread may acquire it. If an exception is thrown during invocation of the synchronized
method, and the synchronized
method does not handle the exception, the monitor for the method is automatically released before the exception is rethrown out of the synchronized
method.
The monitorenter and monitorexit instructions exist to support Java's synchronized
statements. A synchronized
statement acquires a monitor on behalf of the executing thread, executes the body of the statement, then releases the monitor:
void onlyMe(Foo f) {
Compilation of synchronized statements is straightforward:synchronized(f) {
doSomething();
}
}
Method void onlyMe(Foo)
0
aload_1
// Load f
onto operand stack
1
astore_2
// Store it in local variable 2
2
aload_2
// Load local variable 2 ( f
) onto stack
3
monitorenter
// Enter the monitor associated with f
4
aload_0
// Holding the monitor, pass this
and
5
invokevirtual
#5
// call Example.doSomething()V
8
aload_2
// Load local variable 2 ( f
) onto stack
9
monitorexit
// Exit the monitor associated with f
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
From | To | Target a | Type |
4 | 8 | 11 | any |
Содержание | Предыдущий | Следующий | Индекс
Java Virtual Machine Specification
Copyright © 1996, 1997 Sun Microsystems, Inc.
All rights reserved
Please send any comments or corrections to jvm@java.sun.com