Изображение на обложке любезно предоставлено божественная техногенная девушка
При работе с экосистемой 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
.
- Создайте новую Java-программу с помощью диалогового окна редактора.
- Создайте новый 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
. Затем, в зависимости от этого случая, он печатает несколько строк.
- Нажмите на Просмотр -> Показать байт-код с помощью Jclasslib
На панели, которая появляется справа, мы можем увидеть некоторую информацию о байт-коде. Давайте пройдемся по одному:
- Общая информация : В этом разделе показана конкретная информация о версии JVM, которая скомпилировала этот класс, количество констант в пуле констант, флаги доступа для этого класса и некоторые другие счетчики:
- Пул констант : JVM загружает хэш-карту констант для каждого типа, который он видит в пути к классу. Эта карта в основном состоит из буквенных значений, строк и других ссылок на типы или поля. На все значения ссылается уникальный ключ. В нашем примере мы видим строку для
os.name
загружается с ключом#33
:
Интерфейсы+Поля : В этом разделе отображаются любые объявления интерфейса и полей. Поскольку мы ничего не указали, раздел пуст.
Методы : Этот раздел будет содержать сок байт-кода. В нашей программе мы в настоящее время определяем один
основной
метод. Когдаjavac
компилирует этот код, он создаст две записи, одну для метода и одну для методаmain
. Давайте подробнее рассмотрим эти:- : Байт-код, который генерируется для этого метода, следующий:
0 aload_0 1 invokespecial #1> 4 return
В JVM каждый конструктор класса, даже если он не определен, вызывается как вызов <инициализация>
и он предоставляется компилятором. Приведенные выше инструкции являются минимумом, необходимым для вызова .
aload_0
указывает среде выполнения загрузить локальную ссылку с индексом 0 текущего кадра. Это содержит ссылку на >
сам по себе, поэтому следующая инструкция вызывает специальный # 1
является вызовом специального экземпляра, на который ссылаются как на #1. Когда мы вернемся
.
- главная : Байт-код, который генерируется для этого метода, выглядит следующим образом:
0 ldc #52 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, я могу порекомендовать следующие ресурсы:
- Спецификация JVM : Это ссылка по умолчанию для всего, что связано с платформой JVM.
- Jпрофайлер : Профилирование ваших Java-приложений, как у босса.
- Учебная точка : Быстрые и простые учебные пособия
Оригинал: “https://dev.to/theodesp/how-to-read-java-bytecode-with-examples-4p7g”