Рубрики
Без рубрики

Как читать байт-код Java (с примерами)

Узнайте, что такое байт-код Java и каковы лучшие ресурсы, чтобы узнать о нем больше. С пометкой java, jvm, учебник.

Изображение на обложке любезно предоставлено божественная техногенная девушка

При работе с экосистемой JVM важно потратить некоторое время и понять, что происходит за кулисами. Даже на базовом уровне мы должны быть в состоянии понять и объяснить простыми словами, что такое JVM, как работает компиляция, что такое байт-код и как его читать.

В этом уроке мы увидим JVM с высоты 10000 футов, поймем некоторые основные концепции и научимся считывать байт-код из простой программы.

Давайте начнем.

Что такое JVM?

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

Это означает, что код, скомпилированный на машине Linux, и код, скомпилированный на машине Windows, должны работать в JVM в любом случае. Мы можем скопировать скомпилированные файлы .class из linux в Windows и запустить их там без проблем и наоборот.

Другими словами, когда вы устанавливаете Java на свой ПК с Windows, инструмент java будет использовать среду выполнения для конкретной платформы и JIT-компилятор для запуска вашего кода в Windows. С другой стороны, javac скомпилирует ваш .java файлы в общий формат байт-кода.

Сам байт-код представляет собой формат, который соответствует спецификации спецификации виртуальной машины Java. В нем включены различные функции, основанные на текущей версии. Эти функции определяются на основе запросов спецификации Jar или Java и основаны на текущей реализации. Вот список для OpenJDK 9 , например:

102: Process API Updates
110: HTTP 2 Client
143: Improve Contended Locking
158: Unified JVM Logging
165: Compiler Control
193: Variable Handles
197: Segmented Code Cache
199: Smart Java Compilation, Phase Two
200: The Modular JDK
201: Modular Source Code
211: Elide Deprecation Warnings on Import Statements
212: Resolve Lint and Doclint Warnings
213: Milling Project Coin
214: Remove GC Combinations Deprecated in JDK 8
215: Tiered Attribution for javac
216: Process Import Statements Correctly
217: Annotations Pipeline 2.0
219: Datagram Transport Layer Security (DTLS)
220: Modular Run-Time Images
221: Simplified Doclet API
222: jshell: The Java Shell (Read-Eval-Print Loop)
223: New Version-String Scheme
224: HTML5 Javadoc
225: Javadoc Search
226: UTF-8 Property Files
227: Unicode 7.0
228: Add More Diagnostic Commands
229: Create PKCS12 Keystores by Default
231: Remove Launch-Time JRE Version Selection
232: Improve Secure Application Performance
233: Generate Run-Time Compiler Tests Automatically
235: Test Class-File Attributes Generated by javac
236: Parser API for Nashorn
237: Linux/AArch64 Port
238: Multi-Release JAR Files
240: Remove the JVM TI hprof Agent
241: Remove the jhat Tool
243: Java-Level JVM Compiler Interface
244: TLS Application-Layer Protocol Negotiation Extension
245: Validate JVM Command-Line Flag Arguments
246: Leverage CPU Instructions for GHASH and RSA
247: Compile for Older Platform Versions
248: Make G1 the Default Garbage Collector
249: OCSP Stapling for TLS
250: Store Interned Strings in CDS Archives
251: Multi-Resolution Images
252: Use CLDR Locale Data by Default
253: Prepare JavaFX UI Controls & CSS APIs for Modularization
254: Compact Strings
255: Merge Selected Xerces 2.11.0 Updates into JAXP
256: BeanInfo Annotations
257: Update JavaFX/Media to Newer Version of GStreamer
258: HarfBuzz Font-Layout Engine
259: Stack-Walking API
260: Encapsulate Most Internal APIs
261: Module System
262: TIFF Image I/O
263: HiDPI Graphics on Windows and Linux
264: Platform Logging API and Service
265: Marlin Graphics Renderer
266: More Concurrency Updates
267: Unicode 8.0
268: XML Catalogs
269: Convenience Factory Methods for Collections
270: Reserved Stack Areas for Critical Sections
271: Unified GC Logging
272: Platform-Specific Desktop Features
273: DRBG-Based SecureRandom Implementations
274: Enhanced Method Handles
275: Modular Java Application Packaging
276: Dynamic Linking of Language-Defined Object Models
277: Enhanced Deprecation
278: Additional Tests for Humongous Objects in G1
279: Improve Test-Failure Troubleshooting
280: Indify String Concatenation
281: HotSpot C++ Unit-Test Framework
282: jlink: The Java Linker
283: Enable GTK 3 on Linux
284: New HotSpot Build System
285: Spin-Wait Hints
287: SHA-3 Hash Algorithms
288: Disable SHA-1 Certificates
289: Deprecate the Applet API
290: Filter Incoming Serialization Data
291: Deprecate the Concurrent Mark Sweep (CMS) Garbage Collector
292: Implement Selected ECMAScript 6 Features in Nashorn
294: Linux/s390x Port
295: Ahead-of-Time Compilation
297: Unified arm32/arm64 Port
298: Remove Demos and Samples
299: Reorganize Documentation

Как читать байт-код?

Давайте подойдем немного практичнее и попытаемся понять и прочитать байт-код из простой программы.

Для целей этого урока я буду использовать IntelliJ IDEA с плагином ASM Схема байт-кода но вы также можете использовать VSCode с javap .

  1. Создайте новую Java-программу с помощью диалогового окна редактора.
  2. Создайте новый java-файл с именем DetermineOS.java со следующим кодом:
public class DetermineOS {

    public static void main(String[] args) {

        String strOSName = System.getProperty("os.name");

        System.out.print("Display the current OS name example.. OS is ");
        if(strOSName != null)
        {
            if(strOSName.toLowerCase().contains("linux"))
                System.out.println("Linux");
            else
                System.out.print("not Linux");
        }
    }
}

Приведенный выше код просто извлекает os.name системное свойство и проверяет, содержит ли оно строку linux . Затем, в зависимости от этого случая, он печатает несколько строк.

  1. Нажмите на Просмотр -> Показать байт-код с помощью Jclasslib

На панели, которая появляется справа, мы можем увидеть некоторую информацию о байт-коде. Давайте пройдемся по одному:

  • Общая информация : В этом разделе показана конкретная информация о версии JVM, которая скомпилировала этот класс, количество констант в пуле констант, флаги доступа для этого класса и некоторые другие счетчики:
  • Пул констант : JVM загружает хэш-карту констант для каждого типа, который он видит в пути к классу. Эта карта в основном состоит из буквенных значений, строк и других ссылок на типы или поля. На все значения ссылается уникальный ключ. В нашем примере мы видим строку для os.name загружается с ключом #33 :
  • Интерфейсы+Поля : В этом разделе отображаются любые объявления интерфейса и полей. Поскольку мы ничего не указали, раздел пуст.

  • Методы : Этот раздел будет содержать сок байт-кода. В нашей программе мы в настоящее время определяем один основной метод. Когда javac компилирует этот код, он создаст две записи, одну для метода и одну для метода main . Давайте подробнее рассмотрим эти:

    • : Байт-код, который генерируется для этого метода, следующий:
0 aload_0
1 invokespecial #1 >
4 return

В JVM каждый конструктор класса, даже если он не определен, вызывается как вызов <инициализация> и он предоставляется компилятором. Приведенные выше инструкции являются минимумом, необходимым для вызова . aload_0 указывает среде выполнения загрузить локальную ссылку с индексом 0 текущего кадра. Это содержит ссылку на > сам по себе, поэтому следующая инструкция вызывает специальный # 1 является вызовом специального экземпляра, на который ссылаются как на #1. Когда мы вернемся .

  • главная : Байт-код, который генерируется для этого метода, выглядит следующим образом:
0 ldc #5 
 2 invokestatic #6 
 5 astore_1
 6 getstatic #2 
 9 ldc #7 
11 invokevirtual #4 
14 aload_1
15 ifnull 49 (+34)
18 aload_1
19 invokevirtual #8 
22 ldc #9 
24 invokevirtual #10 
27 ifeq 41 (+14)
30 getstatic #2 
33 ldc #11 
35 invokevirtual #12 
38 goto 49 (+11)
41 getstatic #2 
44 ldc #13 
46 invokevirtual #4 
49 return

Это сложнее, но если вы посмотрите на это более внимательно, вы сможете сопоставить его с самим кодом. Панель байт-кода помогает нам здесь, предоставляя ссылки для каждой инструкции. Вот некоторые объяснения для некоторых из них:

0 ldc #5

Выталкивает элемент № 5 из постоянного пула, который “os.name ” в стопку

2 invokestatic #6

Вызывает статический метод, упомянутый в #6 , который является . При этом будет использоваться переменная верхнего кадра стека, которую мы передавали ранее.

5 astore_1

Сохраняет ссылку из верхней позиции стека в локальный кадр с индексом 1. В основном это результат предыдущего вызова invokestatic. Итак, мы завершили следующую операцию:

Строка.getProperty(“os.name “);

15 ifnull 49

Условная ветвь, которая означает. Если значение из текущей вершины стека, взятое предыдущей инструкцией aload_1 , которую мы использовали для хранения результата astore_1 , равно нулю, то перейдите к строке 49, в противном случае перейдите к следующей строке. Строка 49 в нашем байт-коде – это:

49 return

Вы можете продолжить чтение байт-кода и понять, что такое invokevirtual , getstatic или ifeq имею в виду.

  • Атрибуты : Здесь отображается информация о фактическом исходном файле. Например, мы можем видеть имя файла, указывающего на карту постоянного пула:

Примечание : Если вы хотите просмотреть код с помощью инструмента javap, вы можете выполнить следующую команду:

$ javap -c out.production.jvm-experiments.DetermineOS

Я назвал свой проект jvm-эксперименты поэтому вам нужно указать правильный путь, используя имя вашего проекта.

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

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

Оригинал: “https://dev.to/theodesp/how-to-read-java-bytecode-with-examples-4p7g”