ecsimsw

자바 깊이 알기 / 바이트 코드 분석하기 본문

자바 깊이 알기 / 바이트 코드 분석하기

JinHwan Kim 2020. 7. 6. 13:10

자바 깊이 알기 / 바이트 코드 

 

이전 JVM 구조 공부하면서,

 

1. Runtime Constant Pool에 "클래스 / 인스턴스의 상수, 메소드와 필드에 대한 레퍼런스"이 저장된다.

2. Stackframe에서 constant pool을 참조한다.

3. Local Variable Array는 로컬 변수를 담고 있는 배열이다.

 

이렇게 정리했으나, 사실 잘 와닿지 않았다. 그래서 간단하게라도 바이트 코드를 분석해보고 싶었다. 

 

 

바이트 코드 출력하기

 

java 파일을 준비하고 javac로 컴파일한다. 해당 자바 파일의 class 파일이 생성된다. 

javac javaTest.java

 

역어셈블러(javap)로 해당 클래스를 실행하면 바이트코드가 출력된다.

javap -v -p -s javaTest.class

 

리다이렉션(>) 으로 text파일을 지정해 출력을 해당 파일로 보낼 수 있다.

javap -v -p -s javaTest.class > newFile.txt

 

예제

 

Source code

 

public class javaTest { 
  public static void main(String[] args) 
  { 
     int a = 0;
     int b = 1;
     int c = a+b; 
  } 
}

 

Byte code

 

C:\Users\user>javap -v -p -s javaTest.class
Classfile /C:/Users/user/javaTest.class
  Last modified 2020. 7. 6.; size 281 bytes
  MD5 checksum 8fd75ba7e7ef8d945f69528a9e3c2193
  Compiled from "javaTest.java"
public class javaTest
  minor version: 0
  major version: 55
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #2                          // javaTest
  super_class: #3                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #3.#12         // java/lang/Object."<init>":()V
   #2 = Class              #13            // javaTest
   #3 = Class              #14            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               main
   #9 = Utf8               ([Ljava/lang/String;)V
  #10 = Utf8               SourceFile
  #11 = Utf8               javaTest.java
  #12 = NameAndType        #4:#5          // "<init>":()V
  #13 = Utf8               javaTest
  #14 = Utf8               java/lang/Object
{
  public javaTest();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: iconst_0
         1: istore_1
         2: iconst_1
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: istore_3
         8: return
      LineNumberTable:
        line 4: 0
        line 5: 2
        line 6: 4
        line 7: 8
}
SourceFile: "javaTest.java"

 

Constant pool

Constant pool:
   #1 = Methodref          #3.#12         // java/lang/Object."<init>":()V
   #2 = Class              #13            // javaTest
   #3 = Class              #14            // java/lang/Object
   #4 = Utf8  

 

해시(#)은 참조하고 있는 constant pool의 인덱스를 표시한다. 

Methodref은 method 참조를 나타낸다.

 

Methodref #3. #12, 즉 Methodref java/lang/Object."<init>":()V의 위치를 나타내는데, 이는 메소드 영역에 로드된 Object 클래스의 바이트 코드 중, 생성자의 위치를 의미한다.

 

Class는 클래스를 나태내고 Utf8는 클래스나 메소드 등의 식별자를 UTF-8로 인코딩한 값을 나타낸다. 결국 Constant pool은 소스 코드에서 클래스와 그 맴버를 참조(track) 할 수 있도록 하는 역할을 한다.

 

Constant pool : to keep track of the class and it's members.

  -> numberic literal, string literal, class references, feild references, method references

 

 

Default Constructor

  public javaTest();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

 

컴파일러가 기본 생성자를 자동으로 생성해준다.

 

Code: 의 stack에 해당하는 것이 Operand Stack이다.

aload_0은 Local Varialbes Array에서, 0번 인덱스를 로드한다는 말이다. 즉 this를 Operand stack에 push한다. 

invokespecial은 생성자나 수퍼 클래스의 호출을 의미한다. 위 코드의 경우에는 constant pool의 1번 인덱스. 즉 Object의 생성자를 호출했다.

 

베이스 생성자가 호출되고, 본인 생성자를 호출한 후 리턴된 것이다.

 

(javaTest의 생성자가 없어서 그 순서가 제대로 안보이므로, 이후 다음 예제에서 생성자가 포함된 순서를 확인한다.)

 

 

Main

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: iconst_0
         1: istore_1
         2: iconst_1
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: istore_3
         8: return
      LineNumberTable:
        line 4: 0
        line 5: 2
        line 6: 4
        line 7: 8
}

 

Operand Stack이 2, 로컬 변수가 4이다.

 

Operand Stack이 어떤 역할을 하는지는 아래 스택 오버플로우에서 잘 설명되어 있다. https://stackoverflow.com/questions/24427056/what-is-an-operand-stack

 

Operand Stack이 두 개라기 보다, 스택에 쌓이는 층의 개수를 나타내는 것 같다.

로컬 변수는 소스 코드의 int형 a,b,c와 String[]형 args이다. 

 

다음의 순서로 진행된다.


0-1 : iconst로 0을 스택에 push하고, 다시 pop해서 local variable array 인덱스 1에 저장한다. 

2-3 : iconst로 1을 스택에 push하고, 다시 pop해서 local variable array 인덱스 2에 저장한다. 

4-5 : local variable array 인덱스 1,2를 빼서 스택에 push한다.

6    : 스택 맨 위 두개를 pop하여 add 연산하고 결과를 스택에 push한다.

7    : pop하여 local variable array 인덱스 3에 저장한다.

 

명령어는 https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings 를 참고했다.

 

 

정리

 

사실 JVM 구조를 정리하면서도 메모리 별 역할들이 모호했는데, 실제 사용되는 걸 보니 이제 조금 이해가 간다.

 

바이트 코드 보는 것도 재밌고, 예제 바꿔가면서 이렇게하면 stack이 어떻게 늘어나고, 어떻게하면 methodref가 생기는지 보면서 분석하고 준비했는데 제대로 정리를 못한거 같아 아쉽다.

 

 

참고 자료

 

  JVM Interals

 

JVM Internals

Explains the internal architecture of the Java Virtual Machine (JVM) in simple terms using showing key components and how memory is updated during execution.

blog.jamesdbloom.com

  바이트 코드 시작하기 

 

자바 바이트코드 소개

Understanding bytecode and what bytecode is likely to be generated by a Java compiler helps the Java programmer in the same way that knowledge of assembly helps the C or C++ programmer. - IBM developerWorks journal 개발을 하다 보면 때로는 로우

iamsang.com

  컴파일에서 실행까지 

 

Back to the Essence - Java 컴파일에서 실행까지 - (2)

Back to the Essence - Java 컴파일에서 실행까지 - (2)Java 11 JVM 스펙을 기준으로 Java 소스 코드가 어떻게 컴파일되고 실행되는지 살짝 깊게 알아보자. 이번엔 2탄 실행 편이다. 1탄 컴파일 편은 여기에.. �

homoefficio.github.io

  What is an operand stack 

 

What is an operand stack?

I am reading about JVM architecture. Today I read about the concept of the Operand Stack. According to an article: The operand stack is used during the execution of byte code instructions in a

stackoverflow.com

 

  

 

Comments