Глубокое понимание виртуальной машины Java (механизм выполнения байт-кода)

Java JVM
Глубокое понимание виртуальной машины Java (механизм выполнения байт-кода)

Эта статья была впервые опубликована в публичном аккаунте WeChat:BaronTalk

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

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

I. Структура кадра стека времени выполнения

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

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

Цепочка вызовов методов в потоке может быть очень длинной, и многие методы находятся в состоянии выполнения. Для механизма выполнения в активном потоке допустим только кадр стека наверху стека, который называется текущим кадром стека (Current Stack Frame), и метод, связанный с этим кадром стека, становится текущим методом. Все инструкции байт-кода, запускаемые механизмом выполнения, работают с текущим кадром стека.В концептуальной модели типичная структура кадра стека выглядит следующим образом:

таблица локальных переменных

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

стек операндов

Operand Stack (Operand Stack) - это стек LIFO. То же самое, что и локальная таблица переменных, максимальная глубина стека операнда записана на элементы данных атрибутов атрибутов Code Attage Max_Stacks. Каждый операндный стек элементов может быть произвольными типами данных Java, включая длинные и двойные. 32 OFFICED CABED Data Type Stack Bit Емкость, занятая данными типа 1,64 стека 2 емкости. Метод, выполненный в любое время, глубина стека операнда не превысит максимальный набор элементов Max_Stacks.

Когда метод выполняется впервые, стек операндов метода пуст.Во время выполнения метода будут различные инструкции байт-кода для записи и извлечения содержимого из стека операндов, то есть операции push и pop.stack.

динамическая ссылка

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

адрес возврата метода

Когда метод начинает выполняться, есть только два способа выйти из метода.

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

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

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

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

Дополнительная информация

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

2. Вызов метода

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

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

Разобрать

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

Методы языка Java, отвечающие требованию «компилятор знает, что среда выполнения неизменяема», в основном включают две категории: статические методы и приватные методы, причем первые напрямую связаны с типом, а вторые недоступны извне. характеристики определяют, что ни одна из них не может переопределить другие версии посредством наследования или других средств, поэтому все они подходят для синтаксического анализа на этапе загрузки класса.

Соответственно, в виртуальной машине Java предусмотрено 5 инструкций байт-кода для вызова метода, а именно:

  • invokestatic: вызвать статический метод;
  • invokespecial: вызывать методы конструктора экземпляра, приватные методы и методы родительского класса;
  • invokevirtual: вызывать все виртуальные методы;
  • invokeinterface: при вызове метода интерфейса объект, реализующий этот интерфейс, будет определен во время выполнения;
  • invokedynamic: динамически разрешить метод, на который ссылается квалификатор call-site, во время выполнения, а затем выполнить этот метод.

Пока он может быть вызван инструкциями InvokeStatic и InvokeSpecial, уникальная версия вызова может быть определена на этапе синтаксического анализа, который соответствует этому условию, частный метод, конструктор экземпляра, метод родительского класса 4, при загрузке разрешит ссылку на символ на прямая ссылка. Эти методы можно назвать невиртуальными методами, в отличие от других методов, называемых воображаемыми методами (кроме ОКОНЧАТЕЛЬНЫХ).

В дополнение к методам, вызываемым с помощью invokestatic и invokespecial, в Java есть еще один невиртуальный метод, то есть метод, модифицированный с помощью final. Хотя окончательный метод вызывается с помощью инструкции invokevirtual, поскольку он не может быть переопределен и другой версии нет, нет необходимости выполнять полиморфный выбор на получателе метода, или результат полиморфного выбора должен быть уникальным. В Спецификации языка Java четко указано, что окончательный метод является невиртуальным методом.

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

назначать

Объектно-ориентированный язык имеет три основные характеристики: инкапсуляцию, наследование и полиморфизм. Описанная здесь отправка раскроет некоторые из самых основных проявлений полиморфизма, например, как «перегрузка» и «переопределение» реализованы в виртуальной машине Java? Как виртуальная машина определяет правильный целевой метод?

статическая диспетчеризация

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

/**
 * 方法静态分派演示
 *
 * @author baronzhang
 */
public class StaticDispatch {

    private static abstract class Human { }

    private static class Man extends Human { }

    private static class Woman extends Human { }

    private void sayHello(Human guy) {
        System.out.println("Hello, guy!");
    }

    private void sayHello(Man man) {
        System.out.println("Hello, man!");
    }

    private void sayHello(Woman woman) {
        System.out.println("Hello, woman!");
    }

    public static void main(String[] args) {

        Human man = new Man();
        Human woman = new Woman();
        StaticDispatch dispatch = new StaticDispatch();
        dispatch.sayHello(man);
        dispatch.sayHello(woman);
    }
}

Вывод этой программы после запуска выглядит следующим образом:

Hello, guy!
Hello, guy!

К такому выводу может прийти даже немного опытный Java-программист, но почему на самом деле типы параметров, которые мы передаем в метод sayHello(), это Man и Woman, а виртуальная машина выбирает перегрузку Human при выполнении программы? Чтобы понять эту проблему, мы должны сначала прояснить два понятия.

Human man = new Man();

«Человек» в приведенном выше коде называется статическим типом переменной (Static Type) или называется видимым типом (Apparent Type), последний «Man» называется фактическим типом переменной (Actual Type), static тип и фактический тип В программе могут быть некоторые изменения, разница в том, что смена статического типа происходит только при его использовании, статический тип самой переменной не будет изменен, а окончательный статический тип известен при компиляции время; результат фактического изменения типа находится в Он может быть определен только во время выполнения, компилятор не знает, каков фактический тип объекта при компиляции программы.

После разъяснения этих двух концепций давайте посмотрим на два вызова sayHello() в методе main() в классе StaticDispatch.Исходя из того, что получатель метода определил, что это объект «отправка», используемая перегруженная версия зависит полностью зависит от количества и типов данных входящих параметров. В коде определены две переменные с одинаковым статическим типом, но разными фактическими типами, но виртуальная машина (точнее говоря, компилятор) использует статический тип параметра вместо фактического типа в качестве основы для суждения при перегрузке. И статический тип известен во время компиляции, поэтому на этапе компиляции компилятор Javac решит, какую перегруженную версию использовать в соответствии со статическим типом параметра, поэтому в качестве цели вызова выбирается sayHello(Human), а символический ссылка на этот метод записывается в параметры двух инструкций invokevirtual в методе man().

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

Кроме того, хотя компилятор может определить перегруженную версию метода, во многих случаях эта перегруженная версия не является «уникальной», поэтому часто можно определить только «более подходящую» версию.Основная причина этого заключается в том, что литералы не нужно определять, поэтому у литералов не отображаются статические типы, а их статические типы можно понять и вывести только с помощью языковых правил.. В приведенном ниже коде показана так называемая «более подходящая» версия.

/**
 * @author baronzhang
 */
public class Overlaod {

    static void sayHello(Object arg) {
        System.out.println("Hello, Object!");
    }

    static void sayHello(int arg) {
        System.out.println("Hello, int!");
    }

    static void sayHello(long arg) {
        System.out.println("Hello, long!");
    }

    static void sayHello(Character arg) {
        System.out.println("Hello, Character!");
    }

    static void sayHello(char arg) {
        System.out.println("Hello, char!");
    }

    static void sayHello(char... arg) {
        System.out.println("Hello, char...!");
    }

    static void sayHello(Serializable arg) {
        System.out.println("Hello, Serializable!");
    }

    public static void main(String[] args) {
        sayHello('a');
    }
}

Результат выполнения приведенного выше кода:

Hello, char!

Это легко понять, 'a' - это тип данных char, он, естественно, будет искать перегруженный метод с типом параметра char. Если вы закомментируете метод sayHello(chat arg), результат станет следующим:

Hello, int!

В этот момент происходит преобразование типа, 'a' может представлять не только символ, но и число 97, поскольку Unicode-значение символа 'a' является десятичным числом 97, поэтому перегруженный метод с типом параметра int также подходящее. Мы продолжаем комментировать метод sayHello(int arg), и результат становится таким:

Hello, long!

В это время происходят два преобразования типов.После того как 'a' преобразуется в целое число 97, оно далее преобразуется в длинное целое число 97L, которое соответствует перегруженному методу, тип параметра которого - long. Мы продолжаем комментировать метод sayHello(long arg), и результат становится таким:

Hello, Character!

В это время произошла автоматическая упаковка, и «a» был упакован как его тип инкапсуляции java.lang.Character, поэтому перегруженный метод типа Character был сопоставлен. Продолжайте комментировать метод sayHello (Character arg) и вывод становится:

Hello, Serializable!

Причина, по которой вывод здесь "Hello, Serializable!", заключается в том, что java.lang.Serializable — это интерфейс, реализованный классом java.lang.Character. После автоматической упаковки обнаруживается, что класс упаковки не может быть найден, но класс бокса найден Тип интерфейса, который реализует класс, поэтому сразу после этого происходит еще одно автоматическое преобразование. char может быть преобразован в int, но Character никогда не будет преобразован в Integer, его можно безопасно преобразовать только в интерфейс или родительский класс, который он реализует. Character также реализует другой интерфейс java.lang.Comparable.Если есть два перегруженных метода, параметры которых Serializable и Comparable одновременно, они имеют одинаковый приоритет в это время. Компилятор не может определить, в какой тип автоматически преобразовываться, и сообщит, что тип неоднозначен, и откажется от компиляции. Программа должна отображать статический тип указанного литерала при вызове, например: sayHello((Comparable) 'a'), чтобы скомпилировать и пройти. Продолжайте комментировать метод sayHello(Serializable arg), и результат будет таким:

Hello, Object!

В это время char трансформируется в родительский класс после бокса.Если родительских классов несколько, поиск будет начинаться снизу вверх в отношении наследования, и чем ближе к верхнему слою, тем ниже приоритет. Это правило применяется, даже если значение входного параметра вызова метода равно null. Продолжайте комментировать метод sayHello(Serializable arg), и результат будет таким:

Hello, char...!

Перегруженных методов 7 и только один аннотирован.Видно, что приоритет перегрузки параметров переменной длины самый низкий.В это время символ 'a' рассматривается как элемент массива.

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

Динамическая отправка

Динамическая диспетчеризация тесно связана с другим важным проявлением полиморфизма, "Переопределением". Мы по-прежнему понимаем, что такое динамическая диспетчеризация через код.

/**
 * 方法动态分派演示
 *
 * @author baronzhang
 */
public class DynamicDispatch {

    static abstract class Human {

        abstract void sayHello();
    }

    static class Man extends Human {

        @Override
        void sayHello() {
            System.out.println("Man say hello!");
        }
    }

    static class Woman extends Human {
        @Override
        void sayHello() {
            System.out.println("Woman say hello!");
        }
    }

    public static void main(String[] args){

        Human man = new Man();
        Human woman = new Woman();
        man.sayHello();
        woman.sayHello();

        man = new Woman();
        man.sayHello();
    }
}

Результат выполнения кода:

Man say hello!
Woman say hello!
Woman say hello!

Как в приведенном выше коде виртуальная машина определяет, какой метод вызывать? Очевидно, что статический тип здесь больше не определяется, потому что статический тип также является двумя переменными Human, man и woman, которые ведут себя по-разному при вызове метода sayHello(), а переменная man ведет себя по-разному в двух вызовах метода. Причина такого результата в том, что их фактические типы различны. О том, как виртуальная машина отправляет версию исполнения метода через фактический тип, здесь мы не будем рассказывать, если вам интересно, вы можете перейти к исходной работе.

Мы называем этот тип отправки, который определяет версию выполнения метода в соответствии с фактическим типом во время выполнения, какДинамическая отправка.

однократная отправка и многократная отправка

Приемник метода и параметры метода вместе называются томом метода.Это определение впервые пришло из книги "Java and Patterns". В зависимости от того, на чем основана раздача, раздачу можно разделить наразовая отправкаимножественная отправка.

Присваивается единый метод определения суммы, выполненный по одному варианту, выполняется многократная рассылка для определения варианта метода в соответствии с превышением суммы дел.

Мы все еще понимаем по коду (код основан на знаменитой войне 3Q):

/**
 * 单分派、多分派演示
 *
 * @author baronzhang
 */
public class Dispatch {

    static class QQ { }

    static class QiHu360 { }

    static class Father {

        public void hardChoice(QQ qq) {
            System.out.println("Father choice QQ!");
        }

        public void hardChoice(QiHu360 qiHu360) {
            System.out.println("Father choice 360!");
        }
    }

    static class Son extends Father {

        @Override
        public void hardChoice(QQ qq) {
            System.out.println("Son choice QQ!");
        }

        @Override
        public void hardChoice(QiHu360 qiHu360) {
            System.out.println("Son choice 360!");
        }
    }

    public static void main(String[] args) {

        Father father = new Father();
        Father son = new Son();

        father.hardChoice(new QQ());
        son.hardChoice(new QiHu360());
    }
}

Результат вывода кода:

Father choice QQ!
Son choice 360!

Давайте посмотрим на фазу компиляции процесса выбора компилятора, который присваивается статический процесс. На этот раз выберите целевой метод, основанный на двух вещах: во-первых, статический тип является отцом или сыном; второй - эталонный метод QQ все еще QIHU360.Поскольку он выбирается в соответствии с двумя вариантами, статическое назначение языка Java принадлежит нескольким дистрибутивам..

Давайте рассмотрим процесс выбора виртуальных машин на этапе выполнения, то есть процесс динамического выделения. При выполнении son.hardChoice(new QiHu360()), поскольку сигнатура целевого метода была определена как hardChoice(QiHu360) во время компиляции, статический тип и фактический тип параметров не будут иметь никакого влияния на выбор метод, единственный Единственный фактор, который может повлиять на выбор виртуальной машины, это то, является ли фактический тип получателя этого метода Отцом или Сыном. Динамическая диспетчеризация в языке Java — это единичная диспетчеризация, поскольку в качестве основы для выбора используется только один том.

Подводя итог, можно сказать, что язык Java — это язык со статической множественной диспетчеризацией и динамической одинарной диспетчеризацией.

3. Механизм интерпретации и исполнения байт-кода на основе стека

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

объяснить исполнение

Язык Java часто определяют как «интерпретируемый и исполняемый», но с появлением JIT и компиляторов, которые могут напрямую компилировать код Java в собственный код, это утверждение неверно. Интерпретированное выполнение или скомпилированное выполнение будет более точным только тогда, когда будет определено, что объект является конкретной версией реализации Java и режимом работы механизма выполнения.

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

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

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

Многие механизмы выполнения виртуальных машин Java имеют возможность интерпретировать выполнение (выполнение через интерпретатор) и скомпилированное выполнение (выполнение собственного кода с помощью компилятора точно в срок) при выполнении кода Java. Для последней версии Android режим выполнения — AOT + JIT + выполнение интерпретации, у нас будет возможность поговорить об этом позже.

Набор инструкций на основе стека против набора инструкций на основе регистров

Поток инструкций, выдаваемый компилятором Java, в основном основан на стековой архитектуре набора инструкций. Основное преимущество стекового набора инструкций состоит в том, что он переносим: регистры напрямую предоставляются аппаратным обеспечением, и программа напрямую зависит от этих аппаратных регистров, что неизбежно связано с аппаратным обеспечением. Набор инструкций стековой архитектуры имеет некоторые другие преимущества, такие как относительно компактность (каждый байт в байт-коде соответствует инструкции, а многоадресный набор инструкций также должен хранить параметры), а компиляция и реализация проще. (нет необходимости учитывать распределение пространства) проблема, все пространство используется в стеке) и так далее.

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

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

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

напиши в конце

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

Использованная литература:

  • «Глубокое понимание виртуальных машин Java: расширенные функции и рекомендации JVM (2-е издание)»

Если вам нравится моя статья, я сосредоточусь на следующем количестве публичныхBaronTalk,Знай колонкуили вGitHubДобавьте звезду!