Виртуальная машина Java использует концепцию промежуточного байт-кода для обеспечения переносимости между операционными системами и аппаратными платформами. Использование байт-кода позволяет отделить компилятор от среды выполнения и выполнять компиляцию с разных языков программирования для исполнения на JVM. В этой статье мы разберемся с внутренней организацией class-файлов и байт-кода в них и научимся работать с фреймворком ASM для исследования и программной генерации байт-кода для динамического определения новых классов или их компиляции из других языков программирования.  Java Virtual Machine (далее JVM) реализует совмещенную регистровую и стековую модель и байт-код представляет возможности для реализации полноценного ООП (создание и исследование объектов, вызова методов), описания арифметико-логических операций и ветвления, операций со стеком или локальными переменными, преобразования типов. Для символьной записи используется мнемоника, которая также включает в себя тип операндов: *inc, *dec, *add, *sub, *div, *mul, *rem - арифметические операции (первая буква определяет тип операндов, i - integer, l - long, s - short, b - byte, c - character, f - float, d - double, a - reference) <a>2<b> - преобразование типа данных из a в b (например, i2f - integer to float) new - создание объекта athrow - выбрасывание исключения getstatic - получение ссылки на статический метод или поле putfield - изменение значения атрибута объекта invokevirtual - выполнение метода объекта invokedynamic - динамическое выполнение метода (для языков с динамической типизацией) invokespecial - запуск методов собственного объекта invokeinterface - запуск метода через интерфейс (на объекте класса реализации интерфейса) return - выход из метода if*, goto - переход условный или безусловный *load, *store, *const, dup - операции с локальными переменными или стеком и многие другие Инструкции могут содержать константные значения (например, адрес для перехода по goto/if, константа, адрес объекта или null). Арифметико-логические операции и вызовы функций выполняются с использованием стека, но для хранения промежуточных результатов могут использовать локальные переменные. Файл .class кроме непосредственно байт-кода содержит информацию о модификаторах доступа и названии класса, ссылку на родительский класс, список реализованных интерфейсов, методы и свойства класса (включая статические). Ссылка на классы/интерфейсы осуществляется по полному имени (пакет + название) и передается для обнаружения специальному объекту ClassLoader, который по умолчанию ищет файлы в нескольких местоположениях и использует информацию из переменной окружения CLASSPATH (или аргумента командной строки java -cp). Мы не будем подробно разбирать байт-код, поскольку про это было уже написано несколько статей (например, эта), но больше сосредоточимся на анализе и генерации байт-кода изнутри Java-приложения. Существует множество библиотек для работы с байт-кодом, мы поговорим про ObjectWeb ASM, которая используется во множестве проектов для генерации байт-кода (например, Groovy, Nashorn, Mockito, Gradle). Для использования ASM создадим простое приложение с двумя классами и несколькими арифметическими операциями и ветвлением. После сборки проекта изучим полученный class-файл: Здесь мы сможем увидеть версию байт-кода (minor version / major version), флаги (модификаторы доступа для класса main), название класса и его родителя (родительским будет класс java.lang.Object), аннотации и и их аргументы, пул констант (сюда выполняются ссылки как из заголовка, так и из байт-кода), в котором определяются классы, названия и типы методов, любые константы (в т.ч. строки), ссылки на статические поля и методы в других классах (например, на System.out.println). Также ниже отображается мнемоническое представление байт-кода, а также таблицы (локальные переменные, исключения и т.д). Байт-код для метода flip в классе Coin будет выглядеть следующим образом: Здесь мы можем видеть запуск статических методов (результат первого сохраняется в стеке и извлекается как аргумент для второго). Далее полученный результат (ссылка на объект Random) сохраняется в локальной переменной и заново размещается на стеке, после чего выполняется вызов метода nextBoolean от экземпляра объекта класса Random. Результат (boolean) сохраняется в стеке и проверяется на true (ifeq), в случае успеха осуществляется переход на строку 17 (в стек загружается константа #25 из таблицы), при неудаче - продолжается выполнение со строки 14 (загружается константа #23). В любом случае полученное значение снимается со стека и возвращается как результат выполнения метода. Теперь подключим библиотеку ASM и начнем с разбора class-файлов: Разбор файла начинается с загрузки файла класса через org.objectweb.asm.ClassReader , в который передается байтовый массив с содержанием файла, поток или название класса (в последнем случае будет использоваться ClassLoader).  Созданный объект позволяет выполнить запрос данных по самому классу (например, getClassName() возвращает полное название класса, где точки заменены на слэш, getAccess() возвращает битовую маску для модификаторов доступа, getSuperName() - название родительского класса, getInterfaces() - список реализуемых интерфейсов), получить константы (readInt(offset), readDouble(offset), readUTF8(offset, buffer) , readConst(offset, buffer) и т.д.), а также получить экземпляр класса getClass(), откуда уже можно получить информацию о классе с использовать механизмов рефлексии, например, getAnnotation(annotationClass), getField(name), getMethod(name, args).  Для исследования содержимого класса используется паттерн visitor и с использованием метода accept можно передать реализацию класса ClassVisitor с переопределением методов: visitField - вызывается для каждого поля класса (получает информацию о модификаторах, название, тип, описание типов в виде строки, и значение по умолчанию) visitMethod - вызывается для каждого метода (включая конструктор), получает тип результата, описание типов в виде строки и список исключений visitAttribute - для каждого атрибута visitAnnotation - для каждой обнаруженной аннотации visitInnerClass - для каждого вложенного класса visitEnd - вызывается в конце разбора класса Например, разбор class-файла в нашем случае может выглядеть так: Также для анализа можно использовать готовую реализацию Visitor-классов, например ClassNode, который дает обобщенную информацию о свойствах и методах класса и предоставляет доступ к байт-коду методов. Далее для преобразования в мнемоническое представление можно использовать класс Textifier, который используется совместно со специальной реализацией TraceMethodVisitor. Теперь, когда мы умеем анализировать существующий байт-код, попробуем динамически создать новый класс и метод в нем. Будем делать класс Calculator, в котором реализуется метод sum для сложения двух целых чисел (результатом будет тип int). Эквивалентный код на Java может выглядеть таким образом: Для генерации класса нужно будет добавить все необходимые свойства и методы (вместе с набором инструкций, составляющих программу в виде байт-кода). Обязательно также определить конструктор и вызвать конструктор родительского класса. При определении класса также всегда нужно указывать родительский класс (если никакой явно не определен, то java/lang/Object). Сначала создадим пустой класс (без методов) и попробуем обратиться к нему через стандартный ClassLoader: Теперь добавим функцию сложения. Поскольку здесь будут также использоваться локальные переменные (аргументы функции), то нужно будет дополнительно определить метки в байт-коде, для уточнения области видимости, и создать записи в таблице локальных переменных с указанием меток. В этой части статьи мы рассмотрели создание простого байт-кода без выбрасывания исключений, без ветвлений и циклов и без использования статических методов. Во второй части статьи мы разберемся с тем, как описывать реализуемые интерфейсы, делать обработку ошибок и использовать возможности записей (record), которые появились в Java 14. Исходные тексты приложения размещены в репозитории https://github.com/dzolotov/bytecode (ветка part1). Завершить статью хочу полезной рекомендацией. Хочу порекомендовать вам бесплатный вебинар от коллег из OTUS на котором вы рассмотрите, что из себя представляет протокол http. А чтобы лучше закрепить материал, штатными средствами языка Java, напишите простейшие http-клиент и сервер на java.io. Зарегистрироваться на бесплатный вебинар  Flutter Developer@YandexPro, DevOps, IT Architect