Научу вас делать что-то интересное с байт-кодом Java

Java задняя часть JVM переводчик

0. Пишите впереди

Зачем писать эту статью? Главным образом потому, что я уже исследовал некоторые вопросы, связанные с десенсибилизацией журналов.Как написать плагин десенсибилизации LOG4jОн описывает метод написания плагина десенсибилизации логов:

  • Изменение кода непосредственно в toString очень хлопотно и неэффективно. Вам нужно изменить каждый класс, чтобы он был десенсибилизирован, или написать плагин идеи для автоматического изменения toString(). Недостатком является то, что все компиляторы должны открывать плагины, которые недостаточно универсальны.
  • Измените абстрактное синтаксическое дерево и измените метод toString() во время компиляции, точно так же, как Lombok. Это было исследовано ранее, и его сложно разработать. Позже его можно будет обновить, как его написать.
  • При загрузке, реализуя asm-библиотеку интерфейса Instrumentation, измените байт-код файла класса, но есть более проблемное место, чтобы добавить параметр запуска -javaagent:agentjarpath в jvm, это было реализовано, но обнаружено, что это недостаточно после внедрения.Универсальный.

Два, три и два были реализованы в среднесрочной перспективе.Очень интересно развивать это, и мои знания также расширились.Я напишу 4-5 статей в будущем, чтобы показать вам, как реализовать эти интересные инструменты шаг шаг за шагом.. Узнал После этого, благодаря богатому воображению каждого, я верю, что можно реализовать более интересные вещи.

Что может 0.1 байткод

Например, то, что я представлю в этой статье для снижения чувствительности журнала путем изменения байт-кода, на самом деле является изменением байт-кода toString: Вы можете увидеть, как использовать:

@Desensitized
public class StreamDemo1 {


    private User user;
    @DesFiled(MobileDesFilter.class)
    private String name;
    private String idCard;
    @DesFiled(AddressDesFilter.class)
    private List<String> mm;
    public static void main(String[] args) throws IOException {
        StreamDemo1 streamDemo1 = new StreamDemo1();
        streamDemo1.setUser(new User());
        streamDemo1.setName("18428368642");
        streamDemo1.setIdCard("22321321321");
        streamDemo1.setMm(Arrays.asList("北京是朝阳区打撒所大所大","北京是朝阳区打撒所大所大"));
        System.out.println(streamDemo1);
    }
    
    @Override
    public String toString() {
        return "StreamDemo1{" +
                "user=" + user +
                ", name='" + name + '\'' +
                ", idCard='" + idCard + '\'' +
                ", mm=" + mm +
                '}';
    }
}

Этот класс очень распространен, правда? Единственное отличие от других классов сущностей это дополнительная аннотация: @DesFiled(MobileDesFilter.class), с этой аннотацией мы выполняем основной метод: он выведет:

StreamDemo1{user=bean.User@22d8cfe0, name='184****4777', idCard='22321321321', mm=[北京是朝阳区打*****, 北京是朝阳区打*****]}

Видно, что мы явно вошли безМобильный номер номера, почему вывод отсутствуетНет, это магия манипулирования байт-кодом. Конечно, вы также можете расширить свое мышление самостоятельно. Вы можете использовать его для выполнения аспектов aop. Конечно, cglib также является управляемым байт-кодом для аспектов. Вы также можете использовать его, чтобы делать то, что вы хотите.

0.2 Синтаксическое дерево

С другой стороны, я также исследовал реализацию ломбока и обнаружил, что более интересно модифицировать абстрактное синтаксическое дерево.Вы можете себе представить, вы обычно печатаете входные и выходные параметры для каждого метода повторно, что время- затратно и трудозатратно? Вам обычно не хочется ругать людей за отсутствие критических логов, вы обычно боитесь, что написание АОП и попадание в логи с отражением повлияет на производительность? Чтобы решить эту проблему, я сделал осмысленный инструмент slotLog, адрес github:slothlog githubhttps://github.com/lzggsimida123/slothlog.git (конечно, я также прошу вас дать несколько звездочек, O(∩_∩)О, ха-ха~).

@LogInfo
public class DemoService {
    public String hello(String name, int age){
        System.out.println(name + age + "hello");
        return name+age;
    }
    public static void main(String[] args) {
        DemoService demoService = new DemoService();
        demoService.hello("java", 100);
    }
}

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

[INFO ] 2018-07-20 20:02:42,219 DemoService.main invoke start  args: {} 
[INFO ] 2018-07-20 20:02:42,220 DemoService.hello invoke start  name: java ,age: 100 
java100hello
[INFO ] 2018-07-20 20:02:42,221 DemoService.hello invoke end  name: java ,age: 100 , result: java100

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

0.3 Об этой статье

Если вам не нравятся вышеперечисленные вещи, не волнуйтесь. Байт-код — это основа java. Я думаю, что он обязателен для всех программистов Java. Конечно, вы также должны знать об этом. Эта статья является первой статьей в серии. В этой статье в основном рассказывается о том, что такое байт-код. Понимание этой статьи также является основой для последующих глав.

1. Что такое байт-код?

1.1 Машинный код

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

1.2 Байт-код

Байт-код — это двоичный файл, содержащий исполняемую программу и состоящий из последовательности пар код операции/данные. Байт-код — это промежуточное представление программы: нечто среднее между удобочитаемым исходным кодом и машинным кодом. Его часто рассматривают как двоичный файл, содержащий исполняемую программу, больше похожую на объектную модель. Байт-код называется так потому, что каждый код операции обычно имеет длину в один байт, поэтому размер байт-кода выражается в одном байте. Байт-код также состоит из набора опкодов, а опкоды — это фактически операции над стеком, которые могут удалять параметры и адресное пространство, а также могут помещать результаты. JAVA может преобразовывать байт-код в машинный код с помощью JIT (компиляция точно в срок).

Байт-код реализуется через компиляторы и виртуальные машины. Компилятор компилирует исходный код в байт-код, а виртуальная машина на определенной платформе переводит байт-код в непосредственно исполняемые инструкции. В java Javac обычно используется для компиляции исходных файлов в байткоды, то есть наши файлы классов.

В Интернете были найдены две картинки, на которых показан процесс генерации байт-кода компилятором исходного кода java:

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

  • Обычный код (негорячий) — это интерпретатор байт-кода,

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

2. Выполнение байт-кода

2.1 Структура стека кадров JVM:

Вызов метода преобразуется в выполнение байт-кода в JVM, а структура данных выполнения инструкции байт-кода представляет собой кадр стека. То есть элемент стека в стеке виртуальной машины. Виртуальная машина выделяет фрейм стека для каждого метода, потому что стек виртуальной машины — LIFO (последний пришел — первый вышел), поэтому текущий поток — это активный фрейм стека, то есть фрейм стека наверху стека, т. е. называется "CurrentFrame" в спецификации JVM. , метод, соответствующий текущему кадру стека, называется "CurrentMethod". Операция выполнения байт-кода относится к операции, выполняемой над текущей структурой данных кадра стека.

Структура области данных времени выполнения JVM выглядит следующим образом: .

В основном мы обсуждаем здеськадр стекаСтруктура данных: состоит из четырех частей: область локальных переменных, стек операндов, динамическая ссылка и адрес возврата метода.

2.1.1 Таблица локальных переменных:

Таблица локальных переменных представляет собой набор областей хранения значений переменных, используемых для хранения параметров метода и локальных переменных, определенных в методе. Когда программа Java компилируется в файл класса, переменная locals находится в свойстве Code:

Например, после декомпиляции следующего кода вы можете увидеть locals=5.

Емкость локальных переменных основана на слоте переменной (Slot) как наименьшей единице.Слот в 32-битной виртуальной машине может хранить тип данных в пределах 32 бит (логическое, байтовое, символьное, короткое, целое, число с плавающей запятой, ссылка (ссылка) и returnAddress восемь видов).

В то же время ссылка Slot на объект повлияет на GC (если на него ссылаются, он не будет переработан).

Система не присваивает начальные значения локальным переменным, а значит, отсутствует фаза подготовки, как у переменных класса.

Виртуальная машина использует таблицу локальных переменных для завершения процесса передачи значений параметров в список переменных параметров.Если это экземплярный метод (нестатический), то используется слот с 0-м индексом таблицы локальных переменных для передачи ссылки на экземпляр объекта, которому метод принадлежит по умолчанию. , доступ к которому осуществляется через this в методе.

В нашем коде выше сольт 4 Int плюс сольт этого равен 5.

2.1.2 Стек операндов

Механизм выполнения интерпретации виртуальной машины Java называется «механизм выполнения на основе стека», где упомянутый стек является стеком операндов.

Как и таблица локальных переменных, стек операндов также определяет свое пространство для хранения (максимальную длину единицы) во время компиляции и сохраняет его в потоке байтов класса или интерфейса через атрибут Code. Стек операндов также является стеком LIFO. Доступ к нему осуществляется не по индексу, а с помощью стандартных операций со стеком — push и pop. Например, если инструкция помещает значение в стек операндов, другая инструкция может извлечь это значение, чтобы использовать его позже.

Виртуальная машина хранит данные в стеке операндов так же, как и в области локальных переменных: например, для хранения int, long, float, double, reference и returnType. Значения типа byte, short и char перед помещением в стек операндов также преобразуются в int.

2.1.3 Динамическое связывание

Динамическое связывание предназначено для преобразования метода, представленного символической ссылкой, в прямую ссылку на метод. Преобразование в прямую ссылку на этапе загрузки или при первом использовании (преобразование доступа к переменным в ячейки памяти времени выполнения, где осуществляется доступ к структурам хранения для этих переменных) называется статическим разрешением. Динамическое связывание JVM также поддерживает преобразование во время выполнения в прямые ссылки. Его также можно назвать Late Binding, позднее связывание. Динамическое связывание является базовой структурой гибкого объектно-ориентированного программирования Java.

Примечание:

Символическая ссылка — это строка, содержащая достаточно информации, чтобы найти соответствующее местоположение для фактического использования. Вы говорите, например, символическую ссылку на метод, например: "java/io/PrintStream.println:(Ljava/lang/String;)V". Есть информация о классе, имя метода, параметры метода и другая информация.

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

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

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

2.2 Набор инструкций байт-кода

2.2.1 Инструкции по загрузке и хранению

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

1) Инструкции по загрузке локальной переменной в стек операндов включают: iload, iload_, lload, lload, float, fload_, dload, dload_, aload, aload.

2) Инструкции для сохранения значения из стека операндов в таблицу локальных переменных: istore, istore_, lstore, lstore_, fstore, fstore_, dstore, dstore_, astore, astore_

3) Инструкции по загрузке констант в стек операндов: bipush, sipush, ldc, ldc_w, ldc2_w, aconst_null, iconst_ml, iconst_, lconst_, fconst_, dconst_

4) Инструкция индекса доступа к таблице локальных переменных: широкий

2.2.2 Инструкции по эксплуатации

Арифметические инструкции используются для выполнения определенной операции со значениями в двух стеках операндов и восстановления результата на вершину стека операций.

1) Дополнительная инструкция: iadd,ladd,fadd,dadd

2) Инструкция по вычитанию: isub,lsub,fsub,dsub

3) Инструкция по умножению: imul,lmul,fmul,dmul

4) Инструкция дивизии: idiv,ldiv,fdiv,ddiv

5) оставшаяся команда: irem,lrem,frem,drem

6) Отрицательная инструкция: ineg,leng,fneg,dneg

7) Команда водоизмещения: ишл,ishr,iushr,lshl,lshr,lushr

8) Побитовая инструкция ИЛИ: ior,lor

9) Побитовая инструкция И: iand,land

10) Побитовая инструкция XOR: ixor,lxor

11) Инструкция самоинкремента локальной переменной: iinc

12) Инструкция по сравнению: dcmpg,dcmpl,fcmpg,fcmpl,lcmp

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

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

При преобразовании числа с плавающей запятой в целое число виртуальная машина Java использует режим округления до нуля в стандарте IEEE 754. Результат округления этого режима приведет к усечению числа, и все допустимые байты дробной части будут быть отброшены.

2.2.3 Инструкции по преобразованию типов

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

1. тип int на тип long, float, double

2. Длинный тип для плавания, двойной тип

3. плавать в двойной тип

Однако при работе с сужающими преобразованиями типов это необходимо делать явно, используя инструкции преобразования: i2b, i2c, i2s, l2i, f2i, f2l, d2i, d2l и d2f. При сужении int или long до целочисленного типа T просто отбрасывайте содержимое, кроме N младших байтов, где N — длина T. Это может привести к тому, что результат преобразования будет иметь другой знак, чем входное значение.

При сужении значения с плавающей запятой до целочисленного типа T (только для типов int и long) соблюдаются следующие правила преобразования:

1) Если значение с плавающей запятой равно NaN, результатом преобразования будет 0 типа int или long.

2) Если значение с плавающей запятой не равно бесконечности, значение с плавающей запятой округляется с использованием режима округления до нуля IEEE 754 для получения целого числа v, которое равно v, если v находится в пределах диапазона, представленного T

3) В противном случае, согласно знаку v, преобразовать в наибольшее или наименьшее положительное число, которое может представлять T

2.2.4 Инструкции по созданию объектов и доступу

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

1) Инструкция по созданию экземпляра: новый

2) Инструкции по созданию массива: newarray,anewarray,multianewarray

3) Инструкция доступа к полю: getfield,putfield,getstatic,putstatic

4) Загрузить элементы массива в инструкцию стека операндов: baload,caload,saload,iaload,laload,faload,daload,aaload

5) Сохраните значение стека операндов в элемент массива и выполните: bastore,castore,castore,sastore,iastore,fastore,dastore,aastore

6) Возьмем инструкцию длины массива: arraylengthJVMПоддерживает синхронизацию на уровне метода и синхронизацию последовательности инструкций внутри метода, обе операции выполняютсяmoniterосуществленный.

7) Проверьте инструкцию типа экземпляра: instanceof,checkcast

2.2.5 Инструкции по управлению стеком операндов

Точно так же, как при работе со стеком в обычной структуре данных, виртуальная машина Java предоставляет ряд инструкций для прямого управления растяжками операций, в том числе:

1) Извлечь один или два элемента из вершины стека операндов: pop, pop2

2) Скопируйте одно или два значения на вершину стека и поместите скопированное значение или двойную копию значения обратно на вершину стека: dup, dup2, dup_x1, dup2_x1, dup_x2, dup2_x2.

3) Поменять местами два значения в верхней части стека: поменять местами

2.2.6 Инструкции по передаче управления

Пусть JVM продолжит выполнение программы условно или безоговорочно с инструкции, следующей за указанной инструкцией, вместо инструкции передачи управления. Инструкции по передаче управления включают:

1) Условный переход: ifeq, iflt, ifle, ifne, ifgt, ifge, ifnull, ifnotnull,if_cmpeq,if_icmpne,if_icmlt,if_icmpgt и т. д.

2) Составная условная ветвь: tableswitch, lookupswitch

3) Безусловный переход: goto, goto_w, jsr, jsr_w, ret

В JVM есть специальный набор инструкций для работы с операцией сравнения условных ветвей типов int и reference.Чтобы четко не указывать, является ли значение сущности нулевым, существуют специальные инструкции для обнаружения нулевого значения. Операции сравнения условных ветвей логического типа, байтового типа, типа char и короткого типа выполняются с использованием инструкций сравнения типа int, в то время как операции сравнения длинных, плавающих и двойных условных ветвей выполняются с помощью инструкции операции сравнения соответствующего типа и инструкции операции вернет целочисленное значение, которое добавляется в стек операндов, а затем выполняется операция условного сравнения типа int для завершения всего перехода по ветке. Сравнения различных типов со временем преобразуются в операции сравнения типа int.

2.2.7 Вызов метода и инструкции возврата

инструкция invokevirtual: вызывает метод экземпляра объекта, выполняя диспетчеризацию в соответствии с фактическим типом объекта (диспетчеризация виртуальной машины).

Инструкция invokeinterface: вызовите метод интерфейса, найдите объект, который реализует этот метод интерфейса во время выполнения, и найдите соответствующий метод для вызова.

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

invokestatic: вызвать метод класса (статический)

Инструкции возврата метода различаются в зависимости от типа возвращаемого значения, включая ireturn (возвращаемое значение логическое, байтовое, символьное, короткое и целое), lreturn (длинный), freturn, drturn (двойной) и areturn (ссылочный адрес), еще один возврат Для методов void, методов инициализации экземпляра, методов инициализации класса i классов и интерфейсов.

2.2.8 Инструкции по обработке исключений

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

2.2.9 Инструкции по синхронизации

Синхронизация на уровне метода является неявной, не контролируется инструкциями байт-кода и реализуется в операциях вызова и возврата метода. Виртуальная машина определяет, является ли этот метод синхронизированным, по флагу ACC_SYNCHRONIZED в структуре метки метода в пуле констант метода. Когда метод вызывается, вызывающая инструкция проверяет, установлен ли флаг.Если он установлен, поток выполнения удерживает монитор, затем выполняет метод и, наконец, освобождает монитор, когда метод завершен. Синхронизируйте последовательность наборов инструкций, обычно отмеченную синхронизированным блоком.В наборе инструкций JVM есть monitorenter и monitorexit для поддержки синхронизированной семантики.

Большинство инструкций имеют префиксы и/или суффиксы для указания типа их операндов.

3. Анализ примера байт-кода

Этот раздел даст вам анализ того, как шаг за шагом анализировать байт-код.

3.1 Исходный код

Существует простой код, следующий код представляет собой простой DEMO, есть константа, переменная-член класса и три, один конструктор, get(), статический основной метод для вывода информации.

package java8;

public class ByteCodeDemo {
    private static final String name = "xiaoming";
    
    private int age;

    public ByteCodeDemo(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public static void main(String[] args) {
        ByteCodeDemo byteCodeDeomo = new ByteCodeDemo(12);
        System.out.println("name:" + name + "age:" + byteCodeDeomo.getAge());
    }
}

3.2. Декомпиляция

С помощью командной строки найдите путь, по которому находится наш код, и введите следующую команду:

javac ByteCodeDemo.java

javap -p -v ByteCodeDemo

Для команд Javap вы можете использовать справку или обратиться кjavap-команда, здесь мы используем -p, -v для вывода всей информации о классах и членах, а также дополнительной информации (путь к файлу, размер файла, постоянный пул и т. д.)

3.3. Получите следующую информацию

Classfile /Users/lizhao/Documents/RPC/test/src/main/java/java8/ByteCodeDemo.class
  Last modified 2018-5-8; size 861 bytes
  MD5 checksum d225c0249912bec4b11c41a0a52e6418
  Compiled from "ByteCodeDemo.java"
public class java8.ByteCodeDemo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #14.#31        // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#32         // java8/ByteCodeDemo.age:I
   #3 = Class              #33            // java8/ByteCodeDemo
   #4 = Methodref          #3.#34         // java8/ByteCodeDemo."<init>":(I)V
   #5 = Fieldref           #35.#36        // java/lang/System.out:Ljava/io/PrintStream;
   #6 = Class              #37            // java/lang/StringBuilder
   #7 = Methodref          #6.#31         // java/lang/StringBuilder."<init>":()V
   #8 = String             #38            // name:xiaomingage:
   #9 = Methodref          #6.#39         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #10 = Methodref          #3.#40         // java8/ByteCodeDemo.getAge:()I
  #11 = Methodref          #6.#41         // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  #12 = Methodref          #6.#42         // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #13 = Methodref          #43.#44        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #14 = Class              #45            // java/lang/Object
  #15 = Utf8               name
  #16 = Utf8               Ljava/lang/String;
  #17 = Utf8               ConstantValue
  #18 = String             #46            // xiaoming
  #19 = Utf8               age
  #20 = Utf8               I
  #21 = Utf8               <init>
  ....省略部分
  #58 = Utf8               (Ljava/lang/String;)V
{
  private static final java.lang.String name;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL
    ConstantValue: String xiaoming

  private int age;
    descriptor: I
    flags: ACC_PRIVATE

  public java8.ByteCodeDemo(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iload_1
         6: putfield      #2                  // Field age:I
         9: return
      LineNumberTable:
        line 18: 0
        line 19: 4
        line 20: 9

  public int getAge();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field age:I
         4: ireturn
      LineNumberTable:
        line 23: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: new           #3                  // class java8/ByteCodeDemo
         3: dup
         4: bipush        12
         6: invokespecial #4                  // Method "<init>":(I)V
         9: astore_1
        10: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
        13: new           #6                  // class java/lang/StringBuilder
        16: dup
        17: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
        20: ldc           #8                  // String name:xiaomingage:
        22: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        25: aload_1
        26: invokevirtual #10                 // Method getAge:()I
        29: invokevirtual #11                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        32: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        35: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        38: return
      LineNumberTable:
        line 27: 0
        line 28: 10
        line 29: 38
}
SourceFile: "ByteCodeDemo.java"

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

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

Classfile /Users/lizhao/Documents/RPC/test/src/main/java/java8/ByteCodeDemo.class //输出了我们的class文件的完整路径
  Last modified 2018-5-8; size 861 bytes //以及class文件修改时间以及大小
  MD5 checksum d225c0249912bec4b11c41a0a52e6418 //md5校验和
  Compiled from "ByteCodeDemo.java" //从哪个文件编译而来
public class java8.ByteCodeDemo 
  minor version: 0
  major version: 52 //java主版本  major_version.minor_version 组成我们的版本号52.0
  flags: ACC_PUBLIC, ACC_SUPER //public,ACC_SUPER用于兼容早期编译器,新编译器都设置该标记,以在使用 invokespecial指令时对子类方法做特定处理。
Constant pool:
   #1 = Methodref          #14.#31        // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#32         // java8/ByteCodeDemo.age:I
   #3 = Class              #33            // java8/ByteCodeDemo
   .........

Часть информации была аннотирована и объяснена позже, Давайте в основном поговорим о нашем постоянном пуле, постоянном пуле:

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

В теге представлено 11 типов данных, а именно:

  • CONSTANT_Class_info

  • CONSTANT_Integer_info

  • CONSTANT_Long\info

  • CONSTANT_Float_info

  • CONSTANT_Double_info

  • CONSTANT_String_info

  • CONSTANT_Fieldref_info

  • CONSTANT_Methodref_info

  • CONSTANT_InterfaceMethodref_info

  • CONSTANT_NameAndType_info

  • CONSTANT_Utf8_info

Примечание. В байт-коде Java все типы boolean, byte, char и short хранятся в типе int, поэтому в пуле констант нет соответствующего элемента. Введение в постоянный пул можно найти здесь:

http://www.blogjava.net/DLevin/archive/2011/09/05/358033.html

3.5.основной метод анализа

Здесь основной метод копируется отдельно, и поясняется каждое предложение.

Прежде чем смотреть на следующее, вы можете попробовать понять, понимаете ли вы байт-код основного метода.

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V //方法描述,入参是String,返回是void
    flags: ACC_PUBLIC, ACC_STATIC 
    Code:
      stack=3, locals=2, args_size=1 //栈深最大3,局部变量2,args_size入参是1(如果是实体方法会把this也算入参)
         0: new           #3                  // class java8/ByteCodeDemo new指令创建对象,这里引用了常量池的class 所以这里一共占了三行 2个字节是class 
         //一个字节是new,所以下个行号是 0+3 = 3 并把当前申请的空间地址放到栈顶
         3: dup 															//将栈顶cpoy一份再次放入栈顶,也就是我们上面的空间地址
         4: bipush        12									//取常量12放入栈空间
         6: invokespecial #4                  // Method "<init>":(I)V //执行初始化方法这个时候会用到4的栈顶,和3的栈顶,弹出
         9: astore_1													//将栈顶放入局部变量,也就是0的空间地址,这个时候栈是空的
        10: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream; //获取这个方法地址到栈顶
        13: new           #6                  // class java/lang/StringBuilder 把新开辟的空间地址放到栈顶
        16: dup																//复制一份
        17: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V //弹出栈顶
        20: ldc           #8                  // String name:xiaomingage://取常量到栈顶
        22: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;//弹出栈顶两个元素,压入StringBuilder的引用
        25: aload_1														// 把局部变量,也就是我们刚才的空间地址压入
        26: invokevirtual #10                 // Method getAge:()I //弹出栈顶,获取年龄,把年龄压入栈顶
        29: invokevirtual #11                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;//弹出栈顶两个元素,压入StringBuilder
        32: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;//弹出栈顶两个元素,压入toString
        35: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V//弹出栈顶两个元素,此时栈空
        38: return //返回
      LineNumberTable: //字节码偏移量到源代码行号之间的联系
        line 29: 0 
        line 30: 10
        line 31: 38
}

Мышление: После того, как вы разберетесь здесь, вы можете попробовать написать немного сложный байткод самостоятельно, а затем понять и углубить образ.

Наконец

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

Если вы считаете, что эта статья полезна для вас, или вы хотите получить статьи из последующих глав заранее, или если у вас есть какие-либо вопросы и вы хотите предоставить бесплатный VIP-сервис 1 на 1, вы можете подписаться на мою общедоступную учетную запись, и вы можете получить сотни G новейшего обучения java бесплатно.Информационные видеоролики, а также последние материалы интервью, ваше внимание и пересылка - самая большая поддержка для меня, O(∩_∩)O: