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 메소드인 registerNativesObject 클래스 의 바이트코드에도 표시되어 있음을 관찰할 수 있습니다 .

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에 최신 asmasm-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. 바이트코드 보기

그런 다음 ClassReaderTraceClassVisitor사용 하여 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 , getFieldsgetMethods같은 메서드를 제공합니다 .

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. 클래스 파일 생성

그런 다음 ClassPoolClassFile 클래스 를 사용하여 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살펴보았습니다 .

평소와 같이 모든 코드 구현은 GitHub에서 사용할 수  있습니다 .

Junit footer banner