от"Разделение области памяти JVM«В этой статье вы должны понять, что область памяти виртуальной машины Java можно разделить на счетчик программ, стек виртуальной машины Java, стек собственных методов и кучу. Сегодня давайте подробно рассмотрим одну из этих областей — стек виртуальной машины Java.
Позвольте мне сначала объяснить. Заголовок этой статьи носит «интервьюер Ctrip», который подозревается в том, что он является главной вечеринкой. Но есть одна вещь, которую нужно сказать, некоторые читатели оставили сообщение в предыдущей статье, что интервьюер Ctrip спросил его о знании памяти виртуальной машины Java, поэтому сегодняшний заголовок я «использую вопрос, чтобы играть».
По слову «до поздней встречи» я догадываюсь, что этот читатель упал в песок перед этим вопросом интервью. Скажем так, интервьюер очень любит спрашивать о очках знаний виртуальной машины Java, потому что очень хорошо проверять настоящие навыки кандидата, поэтому я планирую написать еще несколько статей на эту тему, надеясь дать вам немного больше помощи. ~
Виртуальная машина Java использует методы в качестве базовой исполнительной единицы, а «Stack Frame» — это базовая структура данных, используемая для поддержки виртуальной машины Java для вызова и выполнения методов. Каждый кадр стека содержит таблицу локальных переменных, стек операндов, динамическую компоновку, адрес возврата метода и некоторую дополнительную дополнительную информацию (например, информацию, относящуюся к отладке, производительности телефонов). Эти понятия упоминались в предыдущих статьях, и были сделаны некоторые краткие введения, но я не думаю, что они достаточно подробны, поэтому в этой статье основное внимание уделяется представлению этих понятий в фреймах стека.
1) Таблица локальных переменных
Таблица локальных переменных используется для хранения локальных переменных метода, а также параметров метода. Когда файл исходного кода Java компилируется в файл класса, максимальный размер таблицы локальных переменных уже определен.
Давайте посмотрим на такой кусок кода.
public class LocalVaraiablesTable {
private void write(int age) {
String name = "沉默王二";
}
}
write()
У метода есть параметр age и имя локальной переменной.
Затем используйте jclasslib Intellij IDEA для просмотра скомпилированного файла байт-кода LocalVariablesTable.class. можно увидетьwrite()
В свойстве Code метода значение Максимум локальных переменных (максимальный размер таблицы локальных переменных) равно 3.
Само собой разумеется, что максимальная емкость таблицы локальных переменных должна быть 2, один возраст, одно имя, почему 3?
Когда вызывается метод-член (нестатический метод), 0-я переменная фактически является ссылкой на объект, которая вызывает метод-член, известный как this. метод вызоваwrite(18)
, который на самом деле вызываетwrite(this, 18)
.
Щелкните свойство Code и просмотрите LocalVariiableTable, чтобы просмотреть подробную информацию.
0-й — это объект типа LocalVariablesTable; первый — это возраст параметра метода, тип int; второй — это имя локальной переменной внутри метода, типа String.
Конечно, размер таблицы локальных переменных не является суммой всех локальных переменных в методе, он связан с типом переменной и областью действия переменной. Когда область действия локальной переменной заканчивается, ее место в таблице локальных переменных заменяется следующей локальной переменной.
Взгляните на код ниже.
public static void method() {
// ①
if (true) {
// ②
String name = "沉默王二";
}
// ③
if(true) {
// ④
int age = 18;
}
// ⑤
}
-
method()
Размер таблицы локальных переменных метода равен 1. Поскольку это статический метод, нет необходимости добавлять this в качестве первого элемента таблицы локальных переменных; - ② Когда локальная переменная имеет имя, размер таблицы локальных переменных становится равным 1;
- ③ Когда область действия имени переменной заканчивается;
- ④ Когда локальная переменная имеет возраст, размер таблицы локальных переменных равен 1;
- На ⑤ область действия переменной age заканчивается;
Что касается области действия локальных переменных, совет 57 в Effective Java:
Минимизация области действия локальных переменных повышает удобочитаемость и ремонтопригодность кода, а также снижает вероятность ошибок.
Здесь у меня есть еще один момент, чтобы напомнить вам. Чтобы сохранить как можно больше места в памяти для кадров стека, слоты в таблице локальных переменных можно использовать повторно, напримерmethod()
Способ продемонстрировал, что означает, что объем разумной помощи для повышения производительности программы.
Емкость таблицы локальных переменных основана на слоте (слот) как наименьшей единице. Слот может содержать 32-битный тип данных (например, int. Конечно, «Спецификация виртуальной машины Java» четко не указывает размер пространства памяти, которое должен занимать слот. , но я думаю, что так проще понять), типы данных, такие как float и double, которые явно занимают 64 бита, занимают два слота рядом друг с другом.
Взгляните на код ниже.
public void solt() {
double d = 1.0;
int i = 1;
}
Вы можете увидеть это с помощью jclasslib,solt()
Значение локальной переменной Maximum для метода равно 4.
Почему оно равно 4? Это только 3 с этим?
Глядя на LocalVariabletable, вы можете видеть, что индекс переменной I - 3, что означает, что переменная D занимает два слота.
2) Стек операндов
Как и в таблице локальных переменных, максимальная глубина стека операндов (стека операндов) также определяется во время компиляции и записывается в максимальный размер стека свойства Code. Когда метод начинает выполняться, стек операндов пуст.Во время выполнения метода будут различные инструкции байт-кода для записи и извлечения данных из стека операндов, то есть операции push и pop.
Взгляните на код ниже.
public class OperandStack {
public void test() {
add(1,2);
}
private int add(int a, int b) {
return a + b;
}
}
Класс OperandStack имеет всего 2 метода:test()
метод называетсяadd()
метод, переданный 2 параметра. С jclasslib вы можете видеть,test()
Значение максимального размера стека для способа составляет 3.
Это связано с тем, что при вызове метода-члена этот и все параметры будут помещены в стек, а после вызова этот и параметры будут извлекаться из стека один за другим. Соответствующие инструкции байт-кода можно просмотреть через панель «Байт-код».
- aload_0 используется для загрузки переменной ссылочного типа с нижним индексом 0 в таблицу локальных переменных, то есть this, в стек операндов;
- iconst_1 используется для загрузки целого числа 1 в стек операндов;
- iconst_2 используется для загрузки целого числа 2 в стек операндов;
- invokevirtual используется для вызова метода-члена объекта;
- pop используется для извлечения значения из вершины стека;
- return — это инструкция возврата метода void.
посмотри сноваadd()
Инструкции байт-кода для метода.
- iload_1 используется для загрузки переменной типа int с нижним индексом 1 в таблице локальных переменных в стек операндов (индекс 0 это this);
- iload_2 используется для загрузки переменной типа int с индексом 2 в таблице локальных переменных в стек операндов;
- iadd используется для операций сложения типа int;
- ireturn возвращает инструкцию для метода, возвращающего целое число.
Тип данных в операнде должен совпадать с инструкцией байт-кода.В качестве примера возьмем приведенную выше инструкцию iadd.Эта инструкция может использоваться только для добавления целочисленных данных.При ее выполнении два данных в верхней части стека должны быть типа int Да, невозможно добавить данные типа long и double с помощью команды iadd.
3) Динамическое связывание
Каждый кадр стека содержит ссылку на метод, которому принадлежит кадр стека в пуле констант времени выполнения.Эта ссылка сохраняется для поддержки динамической компоновки во время вызова метода.
Взгляните на код ниже.
public class DynamicLinking {
static abstract class Human {
protected abstract void sayHello();
}
static class Man extends Human {
@Override
protected void sayHello() {
System.out.println("男人哭吧哭吧不是罪");
}
}
static class Woman extends Human {
@Override
protected void sayHello() {
System.out.println("山下的女人是老虎");
}
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
man = new Woman();
man.sayHello();
}
}
Если вы знаете переписывание Java, вы сможете понять смысл этого кода. Классы Man и Woman расширяют класс Human и переопределяютsayHello()
метод. Взгляните на беговые результаты:
男人哭吧哭吧不是罪
山下的女人是老虎
山下的女人是老虎
Результат этой операции понять нетрудно: ссылочный тип человека — Человек, но он указывает на объект Мужчина, а ссылочный тип женщины — тоже Человек, но указывает на объект Женщина, после чего мужчина указывает на объект новый объект Женщина.
С точки зрения объектно-ориентированного программирования и с точки зрения полиморфизма мы хорошо понимаем текущий результат, но с точки зрения виртуальной машины Java, как она определяет, какой метод мужчина и женщина должны вызывать?
Взгляните на инструкции байт-кода основного метода с jclasslib.
- Строка 1: Новая инструкция создает объект Man и помещает адрес памяти объекта в стек.
- Строка 2: инструкция dup делает копию значения на вершине стека и помещает его на вершину стека. Поскольку следующая инструкция invokespecial будет использовать ссылку на текущий класс, ей необходимо создать копию.
- Строка 3: директива invokespecial используется для вызова конструктора для инициализации.
- Строка 4: astore_1, виртуальная машина Java извлекает ссылку на объект Man из вершины стека и сохраняет ее в локальной переменной man с индексом 1.
- Инструкции в строках 5, 6, 7 и 8 аналогичны инструкциям в строках 1, 2, 3 и 4, за исключением объекта Женщина.
- Строка 9: Инструкция aload_1 помещает локальную переменную man в стек операндов.
- Строка 10: директива invokevirtual вызывает метод-член объекта.
sayHello()
, обратите внимание, что тип объекта в настоящее времяcom/itwanger/jvm/DynamicLinking$Human
. - Строка 11: Инструкция aload_2 помещает локальную переменную woman в стек операндов.
- Строка 12 аналогична строке 10.
Обратите внимание, что с точки зрения байт-кодаman.sayHello()
(строка 10) иwoman.sayHello()
Байт-код (строка 12) точно такой же, но все мы знаем, что целевой метод окончательного выполнения этих двух инструкций не одинаков.
Что именно произошло?
должен отinvokevirtual
Давайте начнем с этой директивы и посмотрим, как она реализует полиморфизм. Согласно Спецификации виртуальной машины Java, процесс разбора инструкций invokevirtual во время выполнения можно разделить на следующие этапы:
1. Найдите фактический тип объекта, на который указывает элемент в верхней части стека операндов, и обозначьте его как C.
2. Если метод, совпадающий с дескриптором в константном пуле, находится в типе C, выполняется проверка прав доступа, при прохождении возвращается прямая ссылка на метод и поиск завершается, в противном случае возвращается результат .java.lang.IllegalAccessError
аномальный.
③、否则,按照继承关系从下往上一次对 C 的各个父类进行第二步的搜索和验证。
④、如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError
аномальный.
Другими словами, инструкция invokevirtual определяет фактический тип среды выполнения на первом шаге, поэтому инструкция invokevirtual в двух вызовах не заканчивается преобразованием символической ссылки метода в пуле констант в прямую ссылку, а также Процесс выбора версии метода на основе фактического типа получателя метода является сутью переопределения Java. Мы называем этот процесс определения исполняемой версии метода во время выполнения на основе фактического типа какдинамическая ссылка.
4) Адрес возврата метода
Когда метод начинает выполняться, есть только два способа выйти из метода:
-
Для нормального выхода может быть возвращаемое значение, переданное вышестоящему вызывающему методу.То, имеет ли метод возвращаемое значение, и тип возвращаемого значения определяются в соответствии с инструкцией, возвращаемой методом.Как упоминалось ранее, ireturn используется для return типа int и return используется для методов void., есть и другие, lreturn для long, freturn для float, dreturn для double и areturn для ссылки.
-
Ненормальный выход, метод столкнулся с аномалием во время выполнения и не получил надлежащую обработку, и в этом случае верхний звонок вернет любое значение.
В любом случае, после выхода из метода вы должны вернуться к точке, где метод был первоначально вызван, прежде чем программа сможет продолжить выполнение. Вообще говоря, когда метод завершается нормально, значение счетчика ПК будет использоваться в качестве адреса возврата, и значение этого счетчика, вероятно, будет сохранено в кадре стека, но не при аварийном выходе.
Процесс выхода из метода фактически эквивалентен извлечению текущего фрейма стека, поэтому следующие возможные операции: восстановление таблицы локальных переменных и стека операндов вышестоящего метода и помещение возвращаемого значения (если есть) в стек вызывающего объекта. стек операндов кадра, настроить значение счетчика PC, найти следующую команду для выполнения и так далее.
Вышеупомянутая часть содержания относится к «Глубокому пониманию виртуальной машины Java» г-на Чжоу Чжимина и «Глубокому пониманию байт-кода JVM» хорошего друга Чжан Я. Очень рекомендую эти две книги.
Новичкам может показаться очень скучным и трудным для понимания, когда они впервые изучают виртуальную машину Java, но после приобретения определенного опыта изучение этих знаний вызовет чувство просветления. Конечно, знание виртуальной машины Java необходимо освоить, потому что оптимизация производительности, собеседования и даже улучшение навыков программирования крайне необходимы.
В «Университете», одной из четырех книг, в начале упоминается концепция, которая называется «обучение на вещах».Анализ Java может сделать нас более уверенными, и все тело наполняется светом «технического мастера». "~
Недавно я провел почти неделю, сортирующую чистую Java-версию чистящих нот, в общей сложности 300 проблемных решений!
Картинки и тексты являются обильными, а скриншоты следующие. Это не просто сухой код, решающий проблему. Многие проблемы дают различные идеи решения проблем, которые действительно улучшат индекс счастья каждого
После 300 вопросов LeetCode я такой толстый! с Java
Я Тихий Король Эр, ставь лайк, давай вместе станем мастерами, 😁!