Здравствуйте, одноклассники, сегодня пришел второй брат, чтобы отдать долг, не забудьте перетащить его в конец статьи и поставить палец вверх, потом вернуться и прочитать внимательно, ладно!
Недавно я изучал виртуальную машину Java и байт-код, чтобы однажды стать действительно крутым технарем! Я не знаю, есть ли у вас такое чувство, то есть, когда я впервые учился программированию, я действительно не хотел видеть лежащие в его основе вещи, поэтому я хотел подойти и напрямую кодировать, но спустя долгое время я всегда почувствовал, что что-то не так~~~~
Поэтому я начал читать книги низкого уровня, такие как «Углубленное понимание компьютерных систем», «Иллюстрированный TCP/IP» и «Углубленное понимание виртуальной машины Java», «CS50 в Гарвардском университете» и « Ускоренный курс информатики», медленно, возникает ощущение прозрения, гм, это ощущение довольно комфортное, и легко плыть (хе-хе).
Я уже публиковал три статьи о виртуальной машине Java и байт-коде, вы можете просмотреть их еще раз.
Содержание этих трех статей по-прежнему очень сложное, и их относительно легко читать, но если вы новичок и вам очень трудно читать, это не имеет значения, я добавлю более полное, более подробное и более популярное, из другого Перспектива врезана. Когда вы закончите, вы можете добавить эти четыре статьи в избранное вместе, и вы можете пережевывать их позже, когда вам будет больше интересно.
01. Байт-код
Компьютеры «глупые» и распознают только 0 и 1, а это означает, что код, который мы пишем, должен в конечном итоге быть скомпилирован в машинный код, прежде чем он сможет быть выполнен компьютером. В начале своего рождения Java выдвинула очень известный лозунг: «Напиши один раз, беги везде".
Write Once, Run Anywhere.
Для этого лозунга мать Java, Sun и другие поставщики виртуальных машин выпустили множество виртуальных машин Java, которые могут работать на разных платформах, и эти виртуальные машины имеют общую функцию, то есть они могут загружать и выполнять виртуальные машины одного и того же типа. , Независимый от платформы байт-код.
С помощью виртуальной машины Java исходный код Java, который мы пишем, не нужно компилировать в соответствующий машинный код в соответствии с различными платформами, а нужно только сгенерировать байт-код, а затем передать файл байт-кода для запуска на разных платформах. Виртуальная машина Java может прочитать его и выполнить.
Сегодняшняя виртуальная машина Java очень мощная, поддерживает не только язык Java, но и многие другие языки программирования, такие как Groovy, Scala, Koltin и другие.
Давайте посмотрим на кусок кода.
public class Main {
private int age = 18;
public int getAge() {
return age;
}
}
После компиляции и генерации файла Main.class его можно использовать в командной строке.xxd Main.class
Откройте файл класса (я использую Intellij IDEA в macOS).
Для этого шестнадцатеричного содержимого, за исключением детеныша кафе в начале, остальное содержимое можно примерно перевести как: Что это...
Не паникуйте, студенты, давайте начнем с "cafe babe", эти 4 байта называютсямагическое число, то есть только файлы классов, начинающиеся с «cafe babe», могут быть приняты виртуальной машиной Java, и эти 4 байта являются идентификатором файла байт-кода.
Переместите глаза вправо, 0000 — это младший номер версии Java, 0037, преобразованный в десятичное число, — это 55, который является основным номером версии, номер версии Java начинается с 45, каждый литр основной версии, номер версии увеличивается на 1 можно запустить режим Шерлока Холмса, рассуждая один раз.
Далее находится пул строковых констант. "файл класса«В этой статье я проанализировал шестнадцатеричное содержание, возможно, у новичков большая голова, на этот раз мы перейдем к более простому для понимания способу.
02. Декомпилировать файлы байт-кода
В Java есть встроенная команда декомпиляции javap, доступ к которой можно получить черезjavap -help
Изучите основы использования javap.
ОК, вводим командуjavap -v -p Main.class
Давайте посмотрим на результат.
Classfile /Users/maweiqing/Documents/GitHub/TechSisterLearnJava/codes/TechSister/target/classes/com/itwanger/jvm/Main.class
Last modified 2021年4月15日; size 385 bytes
SHA-256 checksum 6688843e4f70ae8d83040dc7c8e2dd3694bf10ba7c518a6ea9b88b318a8967c6
Compiled from "Main.java"
public class com.itwanger.jvm.Main
minor version: 0
major version: 55
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #3 // com/itwanger/jvm/Main
super_class: #4 // java/lang/Object
interfaces: 0, fields: 1, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #4.#18 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#19 // com/itwanger/jvm/Main.age:I
#3 = Class #20 // com/itwanger/jvm/Main
#4 = Class #21 // java/lang/Object
#5 = Utf8 age
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/itwanger/jvm/Main;
#14 = Utf8 getAge
#15 = Utf8 ()I
#16 = Utf8 SourceFile
#17 = Utf8 Main.java
#18 = NameAndType #7:#8 // "<init>":()V
#19 = NameAndType #5:#6 // age:I
#20 = Utf8 com/itwanger/jvm/Main
#21 = Utf8 java/lang/Object
{
private int age;
descriptor: I
flags: (0x0002) ACC_PRIVATE
public com.itwanger.jvm.Main();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 18
7: putfield #2 // Field age:I
10: return
LineNumberTable:
line 6: 0
line 7: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/itwanger/jvm/Main;
public int getAge();
descriptor: ()I
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field age:I
4: ireturn
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/itwanger/jvm/Main;
}
SourceFile: "Main.java"
Когда я открыл глаза и посмотрел на него, я почувствовал, что в нем много содержания. Не волнуйтесь, студенты, давайте разберем это построчно.
Линия 1:
Classfile /Users/maweiqing/Documents/GitHub/TechSisterLearnJava/codes/TechSister/target/classes/com/itwanger/jvm/Main.class
Расположение файла байт-кода.
Строка 2:
Last modified 2021年4月15日; size 385 bytes
Дата модификации и размер файла байт-кода.
Строка 3:
SHA-256 checksum 6688843e4f70ae8d83040dc7c8e2dd3694bf10ba7c518a6ea9b88b318a8967c
Значение SHA-256 файла байт-кода.
Строка 4:
Compiled from "Main.java"
Указывает, что файл байт-кода скомпилирован из исходного файла Main.java.
Строка 5:
public class com.itwanger.jvm.Main
Полное имя класса файла байт-кода.
строка 6minor version: 0
, дополнительный номер версии.
строка 7major version: 55
, основной номер версии.
Строка 8:
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
Существует 8 типов тегов доступа к классу.
указывает, что текущий классACC_PUBLIC | ACC_SUPER
. побитовые операторы|
означает, что если соответствующий бит равен 0, результат равен 0, иначе он равен 1, поэтому0x0001 | 0x0020
Результат0x0021
(Для работы его необходимо преобразовать в двоичный файл).
Строка 9:
this_class: #3 // com/itwanger/jvm/Main
Индекс текущего класса указывает на константу с индексом 3 в пуле констант.Видно, что текущий класс является основным классом.
Строка 10:
super_class: #4 // java/lang/Object
Индекс родительского класса указывает на константу с индексом 6 в константном пуле.Видно, что родительским классом текущего класса является класс Object.
Строка 11:
interfaces: 0, fields: 1, methods: 2, attributes: 1
Текущий класс имеет 0 интерфейсов, 1 поле (возраст), 2 метода (write()
метод и конструктор по умолчанию), 1 атрибут (единственный атрибут этого класса — SourceFIle, который содержит информацию об исходном файле).
03. Постоянный пул
Далее следует пул констант, который является наиболее важной частью пула констант в файле байт-кода. Константный пул можно понимать как хранилище ресурсов в файле байт-кода, в котором в основном хранятся два типа информации.
1) Литерал, который чем-то похож на концепцию констант в Java, таких как текстовые строки, конечные константы и т. д.
2) Символические ссылки, которые относятся к концепции принципа компиляции, включая 3 вида:
- Полное имя классов и интерфейсов
- Имя поля и дескриптор (Descriptor)
- имя и дескриптор метода
Виртуальная машина Java динамически компонуется при загрузке файла байт-кода, то есть символические ссылки на поля и методы могут получить реальные адреса памяти только после преобразования во время выполнения. Когда виртуальная машина Java работает, ей необходимо получить соответствующую ссылку на символ из пула констант, а затем проанализировать и преобразовать ее в определенный адрес памяти при создании или запуске класса.
Всего в текущем файле байткода 21 константа, и между ними есть связи, анализировать по одной будет хаотично, мы принимаем метод следования по лозам, глядя сверху вниз, и будем нажмите на связанные константы.
Примечание:
-
#
За индексом следует индекс. Индекс начинается не с 0, а с 1, потому что разработчик считает: «Если вы хотите выразить значение отсутствия ссылки на какую-либо константу, вы можете установить значение индекса равным 0, чтобы указать» ( «Глубокое понимание виртуальной машины Java»). -
=
За числом следует тип константы без префиксаCONSTANT_
и суффикс_info
. -
Индекс, упомянутый в полном тексте, эквивалентен индексу и не является унифицированным для гибкого описания.
1-я константа:
#1 = Methodref #4.#18 // java/lang/Object."<init>":()V
Тип — Methodref, указывающий, что он используется для определения метода, указывающего на константы с индексами 4 и 18 в пуле констант.
4-я константа:
#4 = Class #21 // java/lang/Object
Тип — Class, что указывает на то, что он используется для определения класса (или интерфейса), указывающего на константу с нижним индексом 21 в пуле констант.
21-я константа:
#21 = Utf8 java/lang/Object
Тип Utf8, строка в кодировке UTF-8, значениеjava/lang/Object
.
18-я константа:
#18 = NameAndType #7:#8 // "<init>":()V
Тип — NameAndType, указывающий, что это частичная символическая ссылка на поле или метод, указывающая на константы с нижними индексами 7 и 8 в пуле констант.
7-я константа:
#7 = Utf8 <init>
Тип Utf8, строка в кодировке UTF-8, значение<init>
, указывающий на конструктор.
8-я константа:
#8 = Utf8 ()V
Тип Utf8, строка в кодировке UTF-8, значение()V
, указывающий, что возвращаемое значение метода недействительно.
На данный момент первая константа готова. Комбинация означает, что класс Main использует конструктор по умолчанию, производный от класса Object.
2-я константа:
#2 = Fieldref #3.#19 // com/itwanger/jvm/Main.age:I
Тип — Fieldref, указывающий, что он используется для определения поля, указывающего на константы с нижними индексами 3 и 19 в пуле констант.
3-я константа:
#3 = Class #20 // com/itwanger/jvm/Main
Тип — Class, что указывает на то, что он используется для определения класса (или интерфейса), указывающего на константу с индексом 20 в пуле констант.
19-я константа:
#19 = NameAndType #5:#6 // age:I
Тип — NameAndType, указывающий, что это частичная символическая ссылка на поле или метод, указывающая на константы с нижними индексами 5 и 6 в пуле констант.
5-я константа:
#5 = Utf8 age
Тип Utf8, строка в кодировке UTF-8, значениеage
, указывая, что имя поля — возраст.
6-я константа:
#6 = Utf8 I
Тип Utf8, строка в кодировке UTF-8, значениеI
, указывающий, что поле имеет тип int.
Таблица сопоставления дескрипторов для типов полей показана на следующем рисунке.
Пока вторая константа закончена. Комбинация означает, что объявлено поле age типа int.
04. Коллекция полевых таблиц
Таблицы полей используются для описания переменных, объявленных в интерфейсе или классе, включая переменные класса и переменные-члены, но не локальные переменные, объявленные в методах.
Модификаторы поля обычно:
- Модификаторы доступа, такие как общедоступный частный защищенный
- Модификаторы статических переменных, такие как static
- конечный модификатор
- Модификаторы видимости параллелизма, такие как volatile
- Модификаторы сериализации, такие как переходный
Затем идет тип поля (это могут быть примитивные типы данных, массивы и объекты) и имена.
В файле байт-кода Main.class информация таблицы полей показана ниже.
private int age;
descriptor: I
flags: (0x0002) ACC_PRIVATE
Указывает, что модификатор доступа к полю является закрытым, тип — int, а имя — age.
Флаги доступа к полям очень похожи на флаги доступа к классам.
05. Коллекция таблиц методов
Таблица методов используется для описания методов, объявленных в интерфейсе или классе, включая методы класса и методы-члены, а также конструкторы. Модификаторы методов немного отличаются от полей.Например, volatile и transient нельзя использовать для модификации методов.Например модификаторов для методов больше,таких как synchronized,native,strictfp и abstract.
Следующая часть — это конструктор, тип возвращаемого значения — void, а флаг доступа — общедоступный.
public com.itwanger.jvm.Main();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Рассмотрим подробно свойство Code.
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 18
7: putfield #2 // Field age:I
10: return
LineNumberTable:
line 6: 0
line 7: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/itwanger/jvm/Main;
-
stack — это самый большой стек операндов, и виртуальная машина Java будет выделять глубину стека операндов кадра стека в соответствии с этим значением при работе.
-
locals — это место для хранения, необходимое для локальных переменных, а единицей — слот.Переменные параметров метода и локальные переменные в методе хранятся в таблице локальных переменных.
-
args_size — количество аргументов метода.
Почему значение stack равно 2, значение locals равно 1, а значение args_size равно 1? Разве конструктор по умолчанию не без параметров и локальных переменных?
Это потому, что есть скрытая переменная this, пока это не статический метод, будет объект this текущего класса, который спокойно существует. Это объясняет, почему locals и args_size имеют значение 1. Тогда почему значение стека равно 2? Поскольку инструкция байт-кода invokespecial (вызывающая конструктор родительского класса для инициализации) использует ссылку на текущий класс, aload_0 выполняется дважды, что означает, что размер стека операндов равен 2.
Что касается инструкций байт-кода, мы подробно рассмотрим их позже.
-
LineNumberTable, функцией этого свойства является описание соответствия между номером исходной строки и номером строки байт-кода (смещение байт-кода).
-
LocalVariableTable, функция этого свойства заключается в описании взаимосвязи между локальными переменными в стеке фреймов и переменными, определенными в исходном коде. Если внимательно присмотреться, то можно увидеть тень этого.
Ниже приведен метод-членgetAge()
, тип возвращаемого значения — int, а флаг доступа — общедоступный.
public int getAge();
descriptor: ()I
flags: (0x0001) ACC_PUBLIC
Разобравшись со свойством Code конструктора, посмотрите еще разgetAge()
Легко понять, когда используется атрибут Code метода.
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field age:I
4: ireturn
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/itwanger/jvm/Main;
Максимальный стек операндов равен 1, место для хранения, необходимое для локальных переменных, равно 1, а количество параметров для метода равно 1, поскольку локальные переменные имеют только одно скрытое this, а aload_0 выполняется только один раз в инструкции байт-кода.
Я изначально думал об этой статье и полностью закончил байт-код Java, но я не ожидал, что мне придется снова изучать инструкции байт-кода, это сложно!
На самом деле обучение устроено так: его можно масштабировать по горизонтали или по вертикали. Когда мы впервые изучаем программирование, мы особенно хотим узнать немного больше, что относится к горизонтальному расширению.Когда у нас есть определенный опыт программирования и мы хотим перейти на более высокий уровень, нам нужно расширяться по вертикали, продолжать учиться вглубь и искоренить наши собственные знания системы.
Будь то с точки зрения шестнадцатеричного байт-кода, или с точки зрения jclasslib, графически просматривающего декомпилированный байт-код, или с точки зрения сегодняшней декомпиляции javap, некоторый новый контент может быть обнаружен.
Когда новички впервые соприкоснутся с байткодом, они почувствуют, что их головы стали больше. Вы тренируете, тем больше вы можете это оценить. Ощущение «У меня есть технология, я доминирую над миром» ~
Еще два слова.
Написание кода - это действительно рукоделие, это факт, который должны признать наши программисты, ведь надо стучать пальцами! Это не рукоделие или что-то в этом роде (смеется).
Просто проанализируйте причины, по которым код написан плохо. Во-первых, это может быть только начало работы и написание небольшого кода, если мы написали всего одну-две тысячи строк кода, более элегантный код написать невозможно. Во-вторых, мы можем не понимать шаблоны проектирования. Шаблоны проектирования — это типичные решения распространенных проблем при проектировании программного обеспечения. Они похожи на готовые чертежи, которые можно корректировать в соответствии с потребностями. эти проблемы могут быть только слепыми.
Скажем так, когда шаблоны проектирования хорошо обыграны, код, который вы пишете, будет более элегантным, его будет удобнее читать, а ремонтопригодность и расширяемость будут выше.
Я второй брат который тихо сражается с монстрами.Надеюсь стать сильнее и лысее(нет не нет,красивее) с одноклассниками.Раз увижу,закажуотличныйБар!
Увидимся в следующий раз~