ecsimsw
자바 바이트 코드 분석하기 본문
자바 깊이 알기 / 바이트 코드
이전 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
바이트 코드 시작하기
컴파일에서 실행까지
What is an operand stack
'Language > Java, Kotlin' 카테고리의 다른 글
자바 불변 객체와 메모리 구성 (6) | 2020.11.22 |
---|---|
HashTable 원리와 구현 (4) | 2020.07.13 |
자바는 문자열의 끝을 표시하지 않는다. (1) | 2020.04.13 |
Getter랑 Setter를 왜 써야할까? (15) | 2020.04.02 |
자바의 초기화 순서를 아시나요? (0) | 2020.03.31 |