Java class 파일의 구조: constant pool부터 attribute까지
TOC
Overview
앞 글에서는 Java 소스 코드에서 class 파일까지 흐름을 봤다.
이번 글에서는 class 파일 안에 무엇이 들어가는지만 간단히 본다.
class 파일은 왜 필요한가
Java는 소스 코드를 바로 실행하지 않는다. 먼저 class 파일을 만들고, JVM은 그 파일을 읽어서 실행한다.
즉, class 파일은 Java 실행의 중간 결과물이고, JVM이 프로그램을 이해하는 기본 단위다.
예를 들어 아래 코드를 보자.
public class Sample {
private final String name;
public Sample(String name) {
this.name = name;
}
public String greet() {
return "hello " + name;
}
}
이 파일이 class로 바뀌면 JVM은 Sample이라는 이름, 생성자, greet() 메서드, name 필드를 따로 읽는다.
class 파일의 주요 구성요소
class 파일은 크게 다음 조각으로 읽으면 된다.
magic number와version: 파일 형식과 호환 버전constant pool: 문자열, 클래스명, 메서드/필드 참조- 클래스 정보: 이름, 상속 관계, 접근 제한자
- field / method: 어떤 멤버가 있는지와 그 구현
- attribute: 부가 정보와 디버그 정보
특히 constant pool과 method가 중요하다. constant pool은 실행에 필요한 참조를 모아두고, method에는 실제 bytecode가 들어간다.
Sample 예제로 보면 이렇게 나뉜다.
// source
public class Sample {
private final String name;
public Sample(String name) {
this.name = name;
}
public String greet() {
return "hello " + name;
}
}
// class file에서 읽히는 정보
class name: Sample
super class: java/lang/Object
field: name:Ljava/lang/String;
method: <init>(Ljava/lang/String;)V
method: greet()Ljava/lang/String;
constant pool: "hello ", Sample, name, java/lang/Object
사람이 읽는 문장과 JVM이 읽는 정보는 같은 모양이 아니다.
javap로 보는 class 파일
javap는 class 파일을 볼 때 가장 쉬운 도구다.
javac Sample.java
javap -c -v Sample
이 명령을 실행하면 크게 세 부분을 본다.
Class Metadata
Classfile .../Sample.class
minor version: 0
major version: 61
this_class: #8 // Sample
super_class: #2 // java/lang/Object
interfaces: 0, fields: 1, methods: 3, attributes: 3
이 블록은 클래스 이름, 상속 관계, 필드/메서드 수처럼 파일의 뼈대를 보여준다.
Constant Pool
Constant pool:
#7 = Fieldref #8.#9 // Sample.name:Ljava/lang/String;
#13 = InvokeDynamic #0:#14 // makeConcatWithConstants
#17 = Fieldref #18.#19 // java/lang/System.out:Ljava/io/PrintStream;
#23 = String #24 // kim
#25 = Methodref #8.#26 // Sample."<init>":(Ljava/lang/String;)V
#28 = Methodref #8.#29 // Sample.greet:()Ljava/lang/String;
#32 = Methodref #33.#34 // java/io/PrintStream.println:(Ljava/lang/String;)V
이 블록은 문자열과 메서드/필드 참조 같은 실행용 참조를 보여준다.
Method Bytecode
public Sample(java.lang.String);
0: aload_0
1: invokespecial #1 // Object.<init>()
4: aload_0
5: aload_1
6: putfield #7 // name
public java.lang.String greet();
0: aload_0
1: getfield #7 // name
4: invokedynamic #13
9: areturn
public static void main(java.lang.String[]);
0: getstatic #17 // System.out
3: new #8 // Sample
7: ldc #23 // "kim"
9: invokespecial #25 // <init>
12: invokevirtual #28 // greet
15: invokevirtual #32 // println
18: return
이 블록은 각 메서드가 실제로 어떤 bytecode로 풀리는지 보여준다.
보는 순서는 간단하다.
- header에서 클래스 버전과 상속 관계를 본다.
Constant pool에서 문자열과 참조를 본다.- 각
method의Code에서 bytecode 흐름을 본다.
이렇게 보면 Sample 소스의 각 부분이 class 파일 안에서 어디로 갔는지 바로 연결된다.
정리
class 파일은 소스 코드를 직접 담는 파일이 아니라, JVM이 읽을 메타데이터와 bytecode 묶음이다.
정리하면 이런 질문에 답할 수 있다.
"class" 파일에는 왜 소스 코드 전체가 없지?-> JVM이 필요한 정보만 쓰기 때문이다."문자열은 어디에 저장되지?"->constant pool과 메서드 bytecode에 나뉘어 들어간다."메서드는 어떻게 저장되지?"-> 메서드 시그니처와 실행용 bytecode가 별도로 들어간다.
Next Step
다음 글에서는 class 로딩, 링킹, 초기화를 본다.