1. 개요
바이트코드 분석은 코드 문제 찾기, 코드 프로파일링 및 특정 어노테이션으로 클래스 검색과 같은 여러 가지 이유로 Java 개발자 사이에서 일반적인 관행입니다.
이 기사에서는 Java에서 클래스 파일의 바이트 코드를 보는 방법을 탐색합니다.
2. 바이트코드란 무엇입니까?
바이트코드는 자바 프로그램의 중간 표현으로, JVM이 프로그램을 기계 수준 어셈블리 명령어 로 변환할 수 있도록 합니다 .
자바 프로그램이 컴파일되면 .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 {};
}
기본적으로 바이트코드 출력에는 private access modifier 가 있는 필드/메서드가 포함되지 않습니다 .
3.2. 자바 -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;
// ...
}
여기에서 우리는 private 메소드인 registerNatives 가 Object 클래스 의 바이트코드에도 표시되어 있음을 관찰할 수 있습니다 .
3.3. 자바 -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. 자바 -c
또한 javap 명령을 사용하면 -c 인수 를 사용하여 전체 Java 클래스를 분해할 수 있습니다 .
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 명령을 사용하면 다양한 인수를 사용하여 시스템 정보, 상수 및 내부 유형 서명을 확인할 수 있습니다.
-help 인수 를 사용하여 javap 명령이 지원하는 모든 인수를 나열할 수 있습니다 .
클래스 파일의 바이트코드를 보기 위한 Java 명령줄 솔루션을 살펴보았으므로 이제 몇 가지 바이트코드 조작 라이브러리를 살펴보겠습니다.
4. ASM 사용
ASM 은 널리 사용되는 성능 지향적인 저수준 Java 바이트코드 조작 및 분석 프레임워크입니다.
4.1. 설정
먼저 pom.xml에 최신 asm 및 asm-util Maven 의존성을 추가해 보겠습니다 .
<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. BCEL 사용
Apache Commons BCEL 로 널리 알려진 바이트 코드 엔지니어링 라이브러리 는 Java 클래스 파일을 생성/조작하는 편리한 방법을 제공합니다.
5.1. 메이븐 의존성
평소와 같이 최신 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();
}
여기서 우리는 간결한 형식의 바이트코드를 보기 위해 objectClazz 객체 의 toString 메서드를 사용했습니다 .
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. 자바시스트 사용하기
또한 Java 바이트 코드를 보고 조작하는 고급 API를 제공 하는 Javassist ( Java Programming Assistant) 라이브러리를 사용할 수 있습니다 .
6.1. 메이븐 의존성
먼저 pom.xml에 최신 javassist Maven 의존성을 추가합니다 .
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>
6.2. 클래스 파일 생성
그런 다음 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 개체를 사용하여 클래스 파일을 작성할 수 있는 write 메서드를 사용했습니다 .
// 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. 제이클래스립
또한 IDE 기반 플러그인을 사용하여 클래스 파일의 바이트 코드를 볼 수 있습니다. 예를 들어 IntelliJ IDEA에 사용할 수 있는 jclasslib 바이트코드 뷰어 플러그인을 살펴보겠습니다 .
7.1. 설치
먼저 설정/기본 설정 대화 상자를 사용하여 플러그인을 설치합니다.
7.2. 객체 클래스의 바이트코드 보기
그런 다음 보기 메뉴에서 "Jclasslib와 함께 바이트 코드 표시" 옵션을 선택하여 선택한 개체 클래스의 바이트 코드를 볼 수 있습니다 .
다음으로 Object 클래스 의 바이트 코드를 표시하는 대화 상자가 열립니다 .
7.3. 세부 정보보기
또한 Jclasslib 플러그인 대화 상자를 사용하여 상수 풀, 필드 및 메서드와 같은 바이트 코드의 다양한 세부 정보를 볼 수 있습니다.
마찬가지로 Eclipse IDE를 사용하여 클래스 파일의 바이트 코드를 볼 수 있는 Bytecode Visualizer 플러그인 이 있습니다.
8. 결론
이 사용방법(예제)에서는 Java에서 클래스 파일의 바이트 코드를 보는 방법을 탐색했습니다.
먼저 다양한 인수와 함께 javap 명령을 조사했습니다 . 그런 다음 바이트코드를 보고 조작하는 기능을 제공하는 몇 가지 바이트코드 조작 라이브러리를 살펴보았습니다.
마지막 으로 IntelliJ IDEA에서 바이트 코드를 볼 수 있는 IDE 기반 플러그인 Jclasslib 를 살펴보았습니다 .