Рисунок 13.1 Выполнение приложения в платформе Java
Итак, Java-программа выполняется в режиме интерпретации. Хотя фирма Sun Microsystems декларирует эффективность в числе основных свойств Java-программ, в отношении быстродействия это утверждение, мягко говоря, сомнительно. Интерпретируемая программа в принципе не может выполняться так же быстро, как программа в целевых кодах. Эффективность работы Java-программ зависит от эффективности работы Java VM, и Java VM разных производителей существенно различаются по этому показателю (лидером является фирма IBM). В составе средств разработки Java имеются также "своевременные" (just-in-time) компиляторы (JIT), которые транслируют байт-код Java в коды целевой платформы, результатом чего является исполняемый модуль в формате целевой платформы и системы. Такой модуль выполняется без участия Java VM, и его выполнение происходит эффективнее, чем выполнение интерпретируемого байт-кода, но это уже выходит за пределы платформы Java.
Таким образом, независимость Java-программ от конкретной аппаратной платформы и ОС достигается за счет того, что Java-платформа является дополнительной "прослойкой" между приложением и ОС и вместо специфических системных вызовов API конкретной ОС приложение использует API JRE или базовые конструкции языка
Ниже мы рассматриваем некоторые особенности виртуального "процессора" Java VM, как той платформы, на которой выполняются Java-программы.
13.2 Виртуальная машина Java
Типы данных, с которыми работает Java VM, подразделяются на примитивные и ссылочные. Большинство примитивных типов данных Java VM являются также примитивными типами в языке Java. К ним относятся:
byte - 1-байтное целое со знаком;
short - 2-байтное целое со знаком;
int - 4-байтное целое со знаком;
long - 8-байтное целое со знаком;
float - 4-байтное число с плавающей точкой;
double - 8-байтное число с плавающей точкой;
char - 2-байтный символ Unicode.
В отличие от других языков программирования, размеры типов в языке Java и в Java VM являются постоянными, не зависящими от платформы.
Java VM не оперирует типом boolean, являющимся примитивным типом языка Java. Для выражений языка, оперирующих этим типом, компилятор Java генерирует коды, оперирующие типом int.
Примитивный тип returnAddress в Java VM не имеет соответствия в языке Java. Тип returnAddress представляет собой указатель на команду байт-кода Java и используется в качестве операнда команд передачи управления.
Ссылочные типы в Java VM и в языке Java являются ссылками (указателями) на объекты - экземпляры классов, массивы и интерфейсы (экземпляры классов, реализующих интерфейсы). Спецификации Java VM не определяют внутренней структуры объектов, в большинстве современных Java VM ссылка на объект является указателем на дескриптор объекта, в котором, в свою очередь содержатся два указателя:
на объект типа Class, представляющий информацию типа, в том числе методы и статические данные класса;
на память, выделенную для локальных данных объекта в куче.
Все указатели, с которыми работает Java VM, являются указателями в плоском 32-разрядном адресном пространстве, хотя в реализациях Java VM для 64-разрядных платформ могут использоваться и 64-разрядные указатели.
Основные области памяти, с которыми работает Java VM, показаны на рисунке 13.2.
Рисунок 13.2 Основные области памяти Java VM
Область памяти, называемая кучей, разделяется на две части: область классов и область динамически распределяемой памяти (иногда кучей называют только эту часть памяти). Куча создается при запуске Java VM. Конкретные реализации Java VM могут обеспечивать управление начальным размером кучи и расширение кучи при необходимости.
Класс является основной программной единицей платформы Java, объединяющей в себе данные и методы их обработки. При загрузке класса для него выделяется память в области классов. Каждый класс представляется двумя структурами памяти: областью методов и пулом констант. Область методов содержит исполняемую часть класса - байт-коды методов класса, а также таблицу символических ссылок на внешние методы и переменные. Пул констант содержит литералы класса.
В области динамического распределения выделяется память для размещения объектов. Управление этой областью памяти мы рассматриваем в отдельном разделе.
Java VM поддерживает параллельное выполнение нескольких нитей. Для каждой нити при ее создании Java VM создает набор регистров и стек нити.
Набор регистров включает в себя четыре 32-разрядных регистра:
pc - регистр-указатель на команду;
optop - регистр-указатель на вершину стека операндов текущего кадра;
var - регистр-указатель на массив локальных переменных текущего кадра;
frame - регистр-указатель на среду выполнения текущего метода.
Стек нити представляет собой стек в традиционном понимании, то есть, списковую структуру данных, обслуживаемую по дисциплине "последним пришел - первым ушел". Элементами стека являются кадры (frame) методов. В традиционных блочных языках программирования при помощи стека обеспечиваются вложенные вызовы процедур. Аналогичным образом Java VM через стек нити обеспечивает вложенные вызовы методов, представляя каждый метод кадром в стеке. Новый кадр создается и помещается в вершину стека при вызове метода. Кадр, расположенный в вершине стека является текущим, он соответствует методу, выполняемому в нити в текущий момент. При возврате из метода его кадр удаляется из стека. Управление начальным размером стека нити и возможность его динамического расширения зависит от реализации Java VM. Спецификации Java VM не требуют размещения стека нити в непрерывной области памяти.
Кадр, как было сказано, создается динамически и содержит три основных области.
набор локальных переменных экземпляра класса, на который ссылается регистр var;
стек операндов, на который ссылается регистр optop;
структуры среды выполнения, на которую ссылается регистр frame.
Эти области показаны на рисунке 13.3.
Рисунок 13.3 Структура и связи кадра
Набор локальных переменных представляет собой массив 32-разрядных слов. Данные двойной точности (типы long и double) занимают по два смежных слова в этом массиве. Размер этого массива фиксирован для метода, так как число локальных переменных метода становится известным уже на этапе компиляции. Операнды команд байт-кода, которые оперируют локальными переменными, представляются индексами в этом массиве.
Java VM является стековой машиной. Это означает, что в ней нет регистров общего назначения, и операции производятся над данными, находящимися в стеке. Этой цели служит стек операндов, выделяемый в составе каждого кадра. При выполнении команд байт-кода Java, изменяющих данные, операнды таких команд выбираются из стека операндов, в тот же стек помещаются и результаты выполнения команд.
Среда выполнения метода содержит информацию, необходимую для динамического связывания, возврата из метода и обработки исключений. Код класса (размещенный в области класса) обращается к внешним методам и переменным, используя символические ссылки. Динамическая компоновка переводит символические ссылки в фактические. Среда выполнения содержит ссылки на таблицу символов метода, через которую производятся обращения к внешним методам и переменным.
В среде выполнения содержится также информация, необходимая для возврата из метода: указатель на кадр вызывающего метода, значение регистра pc для возврата, содержимое регистров вызывающего метода и указатель на область для записи возвращаемого значения.
Информация обработки исключений содержит ссылки на секции обработки исключений в методе класса.
Через среду выполнения также происходят обращения к данным, содержащимся в области класса, в том числе, к константам и к переменным класса.
Команды Java VM состоят из однобитного кода операции, а также могут содержать операнды. Число и размер операндов определяются кодом операции, некоторые команды не имеют операндов. Основной алгоритм работы Java VM сводится к простейшему циклу, приведенному на рисунке 13.4.
Рисунок 13.4 Основной цикл работы Java VM
Каждый из типов данных Java VM обрабатывается своими командами. Основные типы команд Java VM:
Команды загрузки и сохранения, в том числе:
загрузка в стек локальной переменной;
сохранение значения из стека в локальной переменной;
загрузка в стек константы (из пула констант).
Команды манипулирования значениями (большинство этих операций работают с операндами из стека и помещают результат в стек), в том числе:
арифметические операции;
побитовые логические операции;
сдвиг;
инкремент (операция работает с операндом - локальной переменной).
Команды преобразования типов.