1. Обзор
Анализ байт-кода является обычной практикой среди разработчиков Java по многим причинам, таким как поиск проблем с кодом, профилирование кода и поиск классов с определенными аннотациями.
В этой статье мы рассмотрим способы просмотра байт-кода файла класса в Java.
2. Что такое байт-код?
Байт-код — это промежуточное представление программы Java, позволяющее JVM преобразовывать программу в инструкции по сборке машинного уровня .
Когда Java-программа компилируется, байт-код генерируется в виде файла .class .
Этот файл .class
содержит невыполнимые инструкции и требует интерпретации JVM.
3. Использование javap
Командная строка Java поставляется с инструментом javap
, который отображает информацию о полях, конструкторах и методах файла класса.
На основе используемых параметров он может разобрать класс и показать инструкции, составляющие байт-код Java.
3.1. джавап
Давайте воспользуемся командой javap
для просмотра байт-кода самого распространенного класса Object :
$ javap java.lang.Object
Вывод команды покажет минимальную конструкцию класса Object
:
public class java.lang.Object {
public java.lang.Object();
public final native java.lang.Class<?> getClass();
public native int hashCode();
public boolean equals(java.lang.Object);
protected native java.lang.Object clone() throws java.lang.CloneNotSupportedException;
public java.lang.String toString();
public final native void notify();
public final native void notifyAll();
public final native void wait(long) throws java.lang.InterruptedException;
public final void wait(long, int) throws java.lang.InterruptedException;
public final void wait() throws java.lang.InterruptedException;
protected void finalize() throws java.lang.Throwable;
static {};
}
По умолчанию вывод байт-кода не будет содержать полей/методов с приватным
модификатором доступа .
3.2. javap-
p
Чтобы просмотреть все классы и члены, мы можем использовать аргумент -p :
public class java.lang.Object {
public java.lang.Object();
private static native void registerNatives();
public final native java.lang.Class<?> getClass();
public native int hashCode();
public boolean equals(java.lang.Object);
protected native java.lang.Object clone() throws java.lang.CloneNotSupportedException;
// ...
}
Здесь мы можем наблюдать, как частный
метод registerNatives
также показан в байт-коде класса Object
.
3.3. javap
-v
Точно так же мы можем использовать аргумент -v
для просмотра подробной информации, такой как размер стека и аргументы для методов класса Object
:
Classfile jar:file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/rt.jar!/java/lang/Object.class
Last modified Mar 15, 2017; size 1497 bytes
MD5 checksum 5916745820b5eb3e5647da3b6cc6ef65
Compiled from "Object.java"
public class java.lang.Object
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #49 // java/lang/StringBuilder
// ...
{
public java.lang.Object();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 37: 0
public final native java.lang.Class<?> getClass();
descriptor: ()Ljava/lang/Class;
flags: ACC_PUBLIC, ACC_FINAL, ACC_NATIVE
Signature: #26 // ()Ljava/lang/Class<*>;
// ...
}
SourceFile: "Object.java"
3.4. javap
-c
Кроме того, команда javap
позволяет дизассемблировать весь класс Java с помощью аргумента -c
:
Compiled from "Object.java"
public class java.lang.Object {
public java.lang.Object();
Code:
0: return
public boolean equals(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: if_acmpne 9
5: iconst_1
6: goto 10
9: iconst_0
10: ireturn
protected native java.lang.Object clone() throws java.lang.CloneNotSupportedException;
// ...
}
Кроме того, команда javap
позволяет нам проверять системную информацию, константы и сигнатуры внутренних типов, используя различные аргументы.
Мы можем перечислить все аргументы, поддерживаемые командой javap
, используя аргумент -help .
Теперь, когда мы рассмотрели решение командной строки Java для просмотра байт-кода файла класса, давайте рассмотрим несколько библиотек для работы с байт-кодом.
4. Использование АСМ
ASM — это популярная ориентированная на производительность низкоуровневая среда для обработки и анализа байт-кода Java.
4.1. Настраивать
Во-первых, давайте добавим последние зависимости asm
и asm-util
Maven в наш pom.xml
:
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>8.0.1</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>8.0.1</version>
</dependency>
4.2. Посмотреть байт-код
Затем мы будем использовать ClassReader
и TraceClassVisitor
для просмотра байт-кода класса Object
:
try {
ClassReader reader = new ClassReader("java.lang.Object");
StringWriter sw = new StringWriter();
TraceClassVisitor tcv = new TraceClassVisitor(new PrintWriter(System.out));
reader.accept(tcv, 0);
} catch (IOException e) {
e.printStackTrace();
}
Здесь мы отметим, что объекту TraceClassVisitor
требуется объект PrintWriter для
извлечения и создания байт-кода:
// class version 52.0 (52)
// access flags 0x21
public class java/lang/Object {
// compiled from: Object.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 37 L0
RETURN
MAXSTACK = 0
MAXLOCALS = 1
// access flags 0x101
public native hashCode()I
// access flags 0x1
public equals(Ljava/lang/Object;)Z
L0
LINENUMBER 149 L0
ALOAD 0
ALOAD 1
IF_ACMPNE L1
ICONST_1
GOTO L2
L1
// ...
}
5. Использование БКЭЛ
Библиотека разработки байт-кода, широко известная как Apache Commons BCEL , предоставляет удобный способ создания файлов классов Java и управления ими.
5.1. Зависимость от Maven
Как обычно, добавим последнюю зависимость bcel
Maven в наш pom.xml
:
<dependency>
<groupId>org.apache.bcel</groupId>
<artifactId>bcel</artifactId>
<version>6.5.0</version>
</dependency>
5.2. Дизассемблировать класс и просмотреть байт-код
Затем мы можем использовать класс Repository для создания объекта
JavaClass
:
try {
JavaClass objectClazz = Repository.lookupClass("java.lang.Object");
System.out.println(objectClazz.toString());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Здесь мы использовали метод toString для объекта
objectClazz
, чтобы увидеть байт-код в сжатом формате: ``
public class java.lang.Object
file name java.lang.Object
compiled from Object.java
compiler version 52.0
access flags 33
constant pool 78 entries
ACC_SUPER flag true
Attribute(s):
SourceFile: Object.java
14 methods:
public void <init>()
private static native void registerNatives()
public final native Class getClass() [Signature: ()Ljava/lang/Class<*>;]
public native int hashCode()
public boolean equals(Object arg1)
protected native Object clone()
throws Exceptions: java.lang.CloneNotSupportedException
public String toString()
public final native void notify()
// ...
Кроме того, класс JavaClass
предоставляет такие методы, как getConstantPool
, getFields
и getMethods
, для просмотра сведений о дизассемблированном классе .
assertEquals(objectClazz.getFileName(), "java.lang.Object");
assertEquals(objectClazz.getMethods().length, 14);
assertTrue(objectClazz.toString().contains("public class java.lang.Object"));
Точно так же методы set*
доступны для манипулирования байт-кодом.
6. Использование Javassist
Кроме того, мы можем использовать библиотеку Javassist
( помощник по программированию на Java)
, которая предоставляет API высокого уровня для просмотра/манипулирования байт-кодом Java.
6.1. Зависимость от Maven
Во-первых, мы добавим последнюю зависимость javassist
Maven в наш pom.xml
:
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>
6.2. Создать ClassFile
Затем мы можем использовать классы ClassPool
и ClassFile
для создания класса Java :
try {
ClassPool cp = ClassPool.getDefault();
ClassFile cf = cp.get("java.lang.Object").getClassFile();
cf.write(new DataOutputStream(new FileOutputStream("Object.class")));
} catch (NotFoundException e) {
e.printStackTrace();
}
Здесь мы использовали метод записи
, который позволяет нам записать файл класса с использованием объекта DataOutputStream
:
// Compiled from Object.java (version 1.8 : 52.0, super bit)
public class java.lang.Object {
// Method descriptor #19 ()V
// Stack: 0, Locals: 1
public Object();
0 return
Line numbers:
[pc: 0, line: 37]
// Method descriptor #19 ()V
private static native void registerNatives();
// Method descriptor #24 ()Ljava/lang/Class;
// Signature: ()Ljava/lang/Class<*>;
public final native java.lang.Class getClass();
// Method descriptor #28 ()I
public native int hashCode();
// ...
Также объект класса ClassFile
предоставляет доступ к пулу констант, полям и методам:
assertEquals(cf.getName(), "java.lang.Object");
assertEquals(cf.getMethods().size(), 14);
7. Jclasslib
Кроме того, мы можем использовать плагин на основе IDE для просмотра байт-кода файла класса. Например, давайте рассмотрим подключаемый модуль jclasslib для просмотра байт
-кода, доступный для IntelliJ IDEA.
7.1. Монтаж
Во-первых, мы установим плагин с помощью диалогового окна Settings/Preferences:
7.2. Просмотр байт-кода класса объекта
Затем мы можем выбрать опцию «Показать байт-код с помощью Jclasslib» в меню «Вид», чтобы просмотреть байт-код выбранного класса объектов :
Далее откроется диалоговое окно для отображения байт-кода класса Object
:
7.3. Посмотреть детали
Кроме того, мы можем увидеть различные детали байт-кода, такие как постоянный пул, поля и методы, используя диалоговое окно плагина Jclasslib:
Точно так же у нас есть плагин Bytecode Visualizer
для просмотра байт-кода файла класса с помощью Eclipse IDE.
8. Заключение
В этом руководстве мы рассмотрели способы просмотра байт-кода файла класса в Java.
Сначала мы рассмотрели команду javap
вместе с ее различными аргументами. Затем мы рассмотрели несколько библиотек для работы с байт-кодом, которые предоставляют функции для просмотра и управления байт-кодом.
Наконец, мы рассмотрели плагин Jclasslib
на основе IDE , который позволяет нам просматривать байт-код в IntelliJ IDEA.
Как обычно, все реализации кода доступны на GitHub .