Байт код приложений что это
Отключение проверки байт-кода для ускорения работы ОС Android
Многие пользователи задаются вопросом увеличения производительности своего Android-гаджета, ведь это не просто «звонилка» в случае со смартфоном, и не просто медиаплеер, в случае, если это планшет.
В данной статье мы разберем очередной твик, который может несколько ускорить работу операционной системы Android.
Большинство пользователей Android-устройств слышали о таких понятиях, как одексированная и деодексированная прошивка. На данную тему мы уже составили отдельную статью. Так вот, отключение проверки байт-кода или disabling verify-bytecode, способно заметно улучшить производительность, особенно на аппаратах со сравнительно небольшим (256-512 Мбайт) объемом оперативной памяти. Изначально считалось, что данный твик актуален только для деодексированных прошивок, но практика показывает, что он полезен и для одексированных версий. Сразу отметим, что всегда перед проведением каких-либо манипуляций с программным обеспечением вашего устройства, настоятельно рекомендуется создавать резервные копии.
Для проведения данной операции нам потребуются, во-первых, Root-права и инсталлированный Root Explorer, а во-вторых, установленный терминал, например Android Terminal Emulator.
Итак, после создания полного бекапа всех данных, открываем терминал и прописываем в него последовательно следующие команды:
setprop dalvik.vm.verify-bytecode false
setprop dalvik.vm.dexopt-flags v=n, o=v
Далее, используя Root Explorer, находим файл build.prop, который находится в памяти устройства в директории system. Открываем его и добавляем туда следующие строки:
Внимание! Если какая-то из строчек уже присутствует в файле, достаточно изменить их значения на указанные выше, дубликатов быть не должно!
После этого снова открываем терминал, куда вводим следующие команды поочередно:
После чего производим перезагрузку устройства и радуемся полученному результату.
Если по какой-либо причине вдруг понадобится отключить действие данного твика, то есть обратно включить проверку байт-кода, то достаточно открыть терминал и ввести туда такие команды:
setprop dalvik.vm.verify-bytecode true
Как результат проделанных операций получаем немного больше свободной оперативной памяти, улучшение производительности устройства и более плавное переключение между приложениями, а также быстрый повторный запуск любых программ. Единственные минусы – некорректная работа некоторых приложений, но справедливости ради скажем, что бывает это крайне редко. Иногда возможны зависания при извлечении или установке карты памяти microSD.
Путь к пониманию байт-кода V8
V8 — это JavaScript-движок Google с открытым кодом. Его используют Chrome, Node.js и многие другие приложения. Этот материал, подготовленный сотрудником Google Франциской Хинкельманн, посвящён описанию формата байт-кода V8. Байт-код довольно просто читать, если понять некоторые базовые вещи.
Конвейер компиляции V8
Зажигание! Пуск! Интерпретатор Ignition, название которого можно перевести как «зажигание», является частью конвейера компиляции V8 с 2016-го года
Когда V8 компилирует JavaScript-код, парсер генерирует абстрактное синтаксическое дерево. Синтаксическое дерево — это древовидное представление синтаксической структуры JS-кода. Интерпретатор Ignition генерирует байт-код из этой структуры данных. Оптимизирующий компилятор TurboFan, в итоге, генерирует из байт-кода оптимизированный машинный код.
Конвейер компиляции V8
Если вы хотите узнать о том, почему V8 имеет два режима исполнения, взгляните на моё выступление с JSConfEU.
Основы байт-кода V8
Байт-код — это абстракция машинного кода. Компилировать байт-код в машинный код проще, если байт-код спроектирован с использованием той же вычислительной модели, которая применяется в физическом процессоре. Именно поэтому интерпретаторы часто являются регистровыми или стековыми машинами.
Интерпретатор Ignition — это регистровая машина с накопительным регистром.
Код слева удобен для людей. Код справа — для машин
Анализ байт-кода функции
Теперь, после того, как мы разобрали основные понятия, посмотрим на байт-код реальной функции.
На немалую часть этих данных мы можем не обращать внимания, сосредоточившись на байт-кодах. Вот описание того, что мы тут видим.
Команда LdaSmi [1] загружает константу 1 в накопительный регистр.
LdaNamedProperty a0, [0], [4]
Теперь содержимое регистров выглядит следующим образом.
Обратите внимание на то, что байт-код, которому посвящён этот материал, используется в V8 версии 6.2, в Chrome 62 и в ещё не выпущенном Node 9. Мы, в Google, постоянно работаем над V8 в направлениях улучшения производительности и уменьшения потребления памяти. В других версиях V8 в байт-коде могут присутствовать некоторые отличия от того, что было описано здесь.
Итоги
На первый взгляд байт-код V8 может показаться довольно-таки загадочным, особенно когда он выводится с массой дополнительных сведений. Однако, как только вы узнаете о том, что Ignition — это регистровая машина с накопительным регистром, вы сможете понять назначение большинства байт-кодов.
Уважаемые читатели! Планируете ли вы анализировать байт-код ваших JS-программ?
Java Bytecode Fundamentals
Разработчики приложений на Java обычно не нуждаются в знании о байт-коде, выполняющемся в виртуальной машине, однако тем, кто занимается разработкой современных фреймворков, компиляторов или даже инструментов Java может понадобиться понимание байт-кода и, возможно, даже понимание того, как его использовать в своих целях. Несмотря на то, что специальные библиотеки типа ASM, cglib, Javassist помогают в использовании байт-кода, необходимо понимание основ для того, чтобы использовать эти библиотеки эффективно.
В статье описаны самые основы, от которых можно отталкиваться в дальнейшем раскапывании данной темы (прим. пер.).
Давайте начнём с простого примера, а именно POJO с одним полем и геттером и сеттером для него.
Когда вы скомпилируете класс, используя команду javac Foo.java, у вас появится файл Foo.class, содержащий байт-код. Вот как его содержание выглядит в HEX-редакторе:
Каждая пара шестнадцатеричных чисел (байт) переводится в опкоды (мнемоника). Было бы жестоко попытаться прочитать это в двоичном формате. Давайте перейдем к мнемоничному представлению.
Класс очень простой, поэтому будет легко увидеть связь между исходным кодом и сгенерированным байт-кодом. Первым делом мы видим, что в байт-код-версии класса компилятор вызывает конструктор по умолчанию (как и написано в спецификациях JVM).
Далее, изучая байт-кодовые инструкции (у нас это aload_0 и aload_1), мы видим, что некоторые из них имеют префиксы типа aload_0 и istore_2. Это относится к типу данных, с которыми оперирует инструкция. Префикс «a» обозначает, что опкод управляет ссылкой на объект. «i», соответственно, управляет integer.
Теперь видно, что это за странные операнды. Например, #2:
const #2 = Field #3.#18; // Foo.bar:Ljava/lang/String;
const #3 = class #19; // Foo
const #18 = NameAndType #5:#6;// bar:Ljava/lang/String;
Отметим, что, каждый код операции помечен номером (0: aload_0). Это указание на позицию инструкции внутри фрейма — дальше объясню, что это значит.
Чтобы понять, как работает байт-код, достаточно взглянуть на модель выполнения. JVM использует модель выполнения на основе стеков. Каждый тред имеет JVM-стек, содержащий фреймы. Например, если мы запустим приложение в дебаггере, то увидим следующие фреймы:
При каждом вызове метода создается новый фрейм. Фрейм состоит из стека операнда, массива локальных переменных и ссылку на пул констант класса выполняемого метода.
Размер массива локальных переменных определяется во время компиляции в зависимости от количества и размера локальных переменных и параметров метода. Стек операндов — LIFO-стек для записи и удаления значений в стеке; размер также определяется во время компиляции. Некоторые опкоды добавляют значения в стек, другие берут из стека операнды, изменяют их состояние и возвращают в стек. Стек операндов также используется для получения значений, возвращаемых методом (return values).
Байткод для этого метода состоит из трёх опкодов. Первый опкод, aload_0, проталкивает в стек значение с индексом 0 из таблицы локальных переменных. Ссылка this в таблице локальных переменных для конструкторов и instance-методов всегда имеет индекс 0. Следующий опкод, getfield, достает поле объекта. Последняя инструкция, areturn, возвращает ссылку из метода.
Так, байткод для метода getBar — 2A B4 00 02 B0. 2A относится к инструкции aload_0, B0 — к areturn. Может показаться странным, что байткод для метода имеет три инструкции, а в массиве байт 5 элементов. Это связано с тем, что getfield (B4) нуждается в двух параметрах (00 02), занимающих позиции 2 и 3 в массиве, отсюда и 5 элементов в массиве. Инструкция areturn сдвигается на 4 позицию.
Таблица локальных переменных
Для иллюстрации того, что происходит с локальными переменными, воспользуемся ещё одним примером:
Здесь две локальных переменных — параметр метода и локальная переменная int b. Вот как выглядит байт-код:
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this LExample;
0 6 1 a I
2 4 2 b I
Метод загружает константу 1 с помощью iconst_1 и ложит её в локальную переменную 2 с помощью istore_2. Теперь в таблице локальных переменных слот 2 занят переменной b, как и ожидалось. Далее, iload_1 загружает значение в стек, iload_2 загружает значение b. iadd выталкивает 2 операнда из стека, добавляет их и возвращает значение метода.
Обработка исключений
Интересный пример того, какой получается байт-код в случае с обработкой исключений, например, для конструкции try-catch-finally.
Байт-код для метода foo():
Компилятор генерирует код для всех сценариев, возможных внутри блока try-catch-finally: finallyMethod() вызывается три раза(!). Блок try скомпилировался так, как будто try не было и он был объединён с finally:
0: aload_0
1: invokespecial #2; //Method tryMethod:()V
4: aload_0
5: invokespecial #3; //Method finallyMethod:()V
Если блок выполняется, то инструкция goto перекидывает выполнение на 30-ю позицию с опкодом return.
Если tryMethod бросит Exception, будет выбран первый подходящий (внутренний) обработчик исключений из таблицы исключений. Из таблицы исключений мы видим, что позиция с перехватом исключения равна 11:
0 4 11 Class java/lang/Exception
Это перекидывает выполнение на catchMethod() и finallyMethod():
11: astore_1
12: aload_0
13: invokespecial #5; //метод catchMethod:()V
16: aload_0
17: invokespecial #3; //метод finallyMethod:()V
Если в процессе выполнения будет брошено другое исключение, мы увидим, что в таблице исключений позиция будет равна 23:
0 4 23 any
11 16 23 any
23 24 23 any
Инструкции, начиная с 23:
23: astore_2
24: aload_0
25: invokespecial #3; //Method finallyMethod:()V
28: aload_2
29: athrow
30: return
Так что finallyMethod() будет выполнен в любом случае, с aload_2 и athrow, бросающим необрабатываемое исключение.
Это всего лишь несколько моментов из области байткода JVM. Большинство было почерпнуто из статьи developerWorks Peter Haggar — Java bytecode: Understanding bytecode makes you a better programmer. Статья немного устарела, но до сих пор актуальна. Руководство пользователя BCEL содержит достойное описание основ байт-кода, поэтому я предложил бы почитать его интересующимся. Кроме того, спецификация виртуальной машины также может быть полезным источником информации, но ее нелегко читать, кроме этого отсутствует графический материал, который бывает полезным при понимании.
В целом, я думаю, что понимание того, как работает байт-код, является важным моментом в углублении своих знаний в Java-программировании, особенно для тех, кто присматривается к фреймворкам, компиляторам JVM-языков или другим утилитам.
Введение в байт-код Java
May 15 · 6 min read
Каждому Java-разработчику известно, какую роль в экосистеме языка играет JVM. Однако большинство не разбирается в том, как работает JVM под капотом. Хотя для разработки на Java это не обязательно, код станет лучше, если вы глубже поймете JVM, потому что так вы будете знать, как каждая строка кода влияет на процессы внутри JVM.
Однако для начала нужно понять, что такое байт-код. Итак, поговорим о вводе и выводе байт-кода Java и о том, как он влияет на JVM во время запуска программы.
Что такое байт-код Java?
Если в какой-то момент профессиональной жизни вы слышали, как проповедуют независимость Java-программ от платформ, скажите спасибо байт-коду.
Как генерируется байт-код?
Как посмотреть байт-код Java?
Если вам хочется увидеть сам байт-код, простейший способ — воспользоваться командной строкой.
Как работает JVM
Прежде чем углубляться в байт-код, стоит понять, как JVM его обрабатывает.
Методы — одна из важнейших составляющих кода для JVM. Среда выполнения Java-программы — это, по сути, набор методов, вызываемых JVM. JVM создает фрейм для каждого такого метода и помещает созданный фрейм наверх стека текущего потока для выполнения.
Фрейм состоит из локальной среды, которая необходима для поддержания его выполнения. Как правило он содержит массив локальных переменных и стек операндов. Посмотрим, что эти элементы из себя представляют.
Массив локальных переменных
Массив локальных переменных, как следует из названия, нужен для хранения локальных переменных в методе. Также он хранит аргументы, которые принимает метод.
Определим два метода: один статический и один метод экземпляра, но схожие во всем остальном.
Локальные массивы переменных для этих методов будут выглядеть следующим образом:
Стек операндов
Стек операндов — это рабочее пространство внутри фрейма метода. Поскольку это стек, вы можете помещать и забирать значения только из верхней его части. Большинство инструкций байт-кода, принадлежащих определенному методу, либо участвуют в помещении значений в стек, либо забирают значения из стека для обработки.
Инструкция байт-кода load и ее расширения нужны для перемещения значения, хранящегося в массиве переменных, в стек. Инструкция store применяется для извлечения значений из стека и сохранения в массиве переменных. Существуют и другие инструкции, которые извлекают значения из стека для обработки.
Посмотрим в байт-код
Ради возможности вглядеться в байт-код, я написал простой Java-класс:
Деконструкция байт-кода
Здесь важно отметить еще одно: индексы, заданные инструкциям байт-кода — как видим, они не увеличиваются на единицу для каждой новой инструкции.
Число перед инструкцией указывает на индекс ее начального байта. А любой байт-код состоит из однобайтовых опкодов, за которыми следует ноль или более операндов.
Вывод
Надеюсь, вам удалось узнать кое-что новое о том, как работает байт-код Java. С этим более четким знанием вы сможете лучше писать код. Можете даже поэкспериментировать с самим байт-кодом во время выполнения программы, воспользовавшись такими библиотеками, как ASM.
Отключаем проверку байт-кода или ускоряем работу android
Все про Android
В данной статье я опишу как отключить проверку байт-кода(disabling verify-bytecode).
Q: Зачем это делать?
A: Данный твик дает прирост производительности в деодексированной прошивке как при одексированной. Актуально на аппаратах с размером оперативной памяти 256-512мб!
Q: Опасно ли это?
A: Все зависит от прямоты ваших рук и желания делать бэкап.
Непосредственно инструкция:
Шаг 1
Делаем полный бэкап данных! Описывать как делать бэкап не буду. Если не умеете- учитесь. Инструкций море!
Шаг 2
Открываем эмулятор терминала на телефоне и вводим следующие команды:
В качестве иллюстрации:
Если какая-либо из строк уже есть в файле, то измените их на данные значения! Главное чтобы не было дубликатов!
В качестве иллюстрации:
Шаг 4
Открываем снова эмулятор терминал и вводим:
Отключение твика:
Если вы решили отключить данный твик, т.е. включить проверку байт-кода, тогда открываем терминал и вводим:
Итоги:
В итоге мы получим небольшой прирост оперативной памяти, прирост производительности, более плавное переключение приложений, быстрый повторный запуск приложений.
Но, стоит отметить, что возможна некорректная некоторых приложений(не заметил) и у некоторых наблюдаются зависания при вынимании/вставки sd-карты!