devseop08 님의 블로그

[JVM] 클래스 파일 구조 본문

Language/Java

[JVM] 클래스 파일 구조

devseop08 2025. 6. 7. 11:07

클래스 파일의 구조

  • 모든 클래스 파일은 각각 하나의 클래스 또는 인터페이스를 정의한다.(단, package-info.class와 module-info.class는 특정 클래스나 인터페이스를 정의하지 않는다. 대신 해당 패키지와 모듈에 대한 정보를 기술)
  • 반면 클래스나 인터페이스를 꼭 파일에 담아둘 필요는 없다. 동적으로 생성하여 클래스 로더에 직접 제공할 수도 있다.
  • 일단 클래스나 인터페이스가 따라야 하는 형식을 '클래스 파일 형식'이라고 지칭하자
  • 클래스나 인터페이스가 디스크에 파일 형태로 존재할 필요는 없다.
  • 클래스 파일은 바이트를 하나의 단위로 하는 이진 스트림 집합체다.
  • 각 데이터 항목이 정해진 순서에 맞게, 구분 기호 없이 조밀하게 나열 => 클래스 파일 전체가 낭비되는 공간 없이 프로그램을 실행하는데 꼭 필요한 데이터로 채워진다.
  • 1바이트가 넘는 데이터 항목은 바이트 단위로 분할, 이 때 큰 단위의 바이트가 먼저 저장되는 빅 엔디언 방식으로 표현된다.
  • 빅 엔디언 방식은 가장 큰 단위의 바이트가 가장 낮은 주소에, 가장 작은 단위의 바이트가 가장 높은 주소에 저장된다. 그 반대 방식은 리틀 엔디언이라고 한다.
  • <<자바 가상 머신 명세>>에 따르면 클래스 파일에 데이터를 저장하는 데는 C언어의 구조체와 비슷한 의사 구조를 이용
  • 이 의사 구조에는 '부호 없는 숫자'와 '테이블'이라는 두 가지 데이터 타입만 존재한다.
    • 부호 없는 숫자: 기본 데이터 타입을 표현
      • u1, u2, u4, u8은 각각 1바이트, 2바이트, 4바이트, 8바이트 의미
      • 숫자, 인덱스 참조, 수량값을 기술하거나 UTF-8로 인코딩된 문자열 값을 구성
    • 테이블 : 복합 데이터 타입(여러 개의 부호 없는 숫자나 또 다른 테이블로 구성) 표현
      • 구분이 쉽도록 테이블 이름은 관례적으로 '_info'로 끝난다.
      • 테이블은 계층적으로 구성된 복합 구조의 데이터를 설명하는 데 사용
      • 클래스 파일 전체는 본질적으로 테이블이다.
// 클래스 파일 구조
ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
  • 같은 타입의 데이터 여러 개를 표현할 때 그 개수가 정해져 있지 않다면 개수를 알려 주는 타입이 바로 앞에 등장한다. '*_count' 형태의 항목들이 여기 속한다.
  • 이처럼 {개수 + 개수만큼의 데이터 타입} 형태를 해당 타입의 '컬렉션'이라고 한다.
  • 클래스 구조는 XML 같은 언어를 이용하지 않는다.
  • 구분자가 없기 때문에 위의 클래스 구조 정의의 데이터 항목은 데이터가 저장되는 바이트 순서, 각 바이트의 의미, 길이, 순서가 모두 엄격하게 제한되며 변경 불가하다.
  • 클래스 파일의 바이트 순서는 빅 엔디언이다.

매직 넘버와 클래스 파일의 버전

  • 모든 클래스 파일의 처음 4바이트는 매직 넘버로 시작한다.
  • 매직 넘버는 가사 머신이 허용하는 클래스 파일인지 여부를 빠르게 확인하는 용도로만 쓰인다.
  • 클래스 파일의 매직 넘버는 0xCAFEBABE다.
  • 매직 넘버 다음의 4바이트는 클래스 파일의 버전 번호다.
  • 5 ~ 6번째 바이트는 마이너 버전을, 7 ~ 8번째 바이트는 메이저 버전을 뜻한다.
  • 자바 버전 번호는 45부터 시작, JDK 1.1 이후 주요 JDK 릴리스의 메이저 버전은 1씩 증가(JDK 1.0 ~ 1.1은 45.0 ~ 45.3 사용)
  • 상위 버전 JDK는 하위 버전을 인식할 수 있지만 하위 버전 JDK에서 상위 버전의 클래스 파일을 실행할 수는 없다.
  • <<자바 가상 머신 명세>>의 '클래스 파일 검증' 절에서 파일 형식이 변경되지 않았더라도 상위 버전의 클래스 파일을 실행하면 안 된다고 규정
public class TestClass {
    private int m;

    public int inc() {
        return m + 1;
    }
}

  • HxD 십육진수 편집기로 실제 클래스 파일의 기계어 코드를 볼 수 있다.
  • 첫 번째 4바이트에서 0xCAFEBABE 값을 확인할 수 있다.
  • 5~6번째 바이트는 마이너 버전인 0x0000이고, 이어서 메이저 버전인 0x003D가 이어진다.
  • 십육진수 3D는 십진수로 61이다. 이 버전 번호로부터 이 클래스 파일은 JDK 17 이상의 가상 머신에서 실행할 수 있음을 알 수 있다.
  • 마이너 버전 번호는 JDK 1.x 이전에 잠시 사용되었다.

상수 풀

  • 버전 번호 다음은 상수 풀 항목이다.
  • 상수 풀은 클래스 파일의 자원 창고로, 클래스 파일 구조에서 다른 클래스와 가장 많이 연관된 부분이다.
  • 차지하는 공간도 대체로 가장 크다.

  • 상수 풀에 들어있는 상수의 수는 고정적이지 않으므로 상수 풀 항목들에 앞서 항목 개수를 알려주는 u2 타입 데이터가 필요
  • TestClass의 상수 풀 크기는 십육진수로 0x0016으로 십진수로는 22에 해당, 상수 풀에는 상수가 21개 존재하며 인덱스 범위는 1~21까지다.
  • 클래스 파일 형식 설계자가 0번째 상수를 비운 이유가 있다.
  • 상수 풀 인덱스를 가리키는 데이터에서 '상수 풀 항목을 참조하지 않음'을 표현해야 하는 특수한 경우에 인덱스를 0으로 설정하도록 한 것이고 그 외에 인덱스 컬렉션, 필드 테이블 컬렉션, 메서드 테이블 컬렉션 등의 원소 개수는 모두 0부터 센다.
  • 상수 풀에 담기는 상수 유형은 리터럴과 심벌 참조 두 가지
    • 리터럴은 자바 언어 수준에서 이야기 하는 상수(final로 선언된 문자열이나 상수)
    • 심벌 참조는 컴파일과 관련된 개념으로 다음 유형의 상수들 포함
        1. 필드 이름과 서술자
        1. 메서드 이름과 서술자
        1. 메서드 핸들과 메서드 타입
        1. 클래스와 인터페이스의 완전한 이름
        1. 모듈에서 익스포트하거나 임포트하는 패키지
        1. 동적으로 계산되는 호출 사이트와 동적으로 계산되는 상수
  • 자바 코드를 컴파일할 때는 C, C++과 달리 링크 단계가 없다.
  • 자바에서 링크는 가상 머신이 클래스 파일을 로딩하는 JVM 런타임에 동적으로 이루어진다.
  • 가상 머신이 필드와 메서드의 심벌 참조를 JVM 런타임에 변환하지 않으면 각 항목의 실제 메모리 주소를 알 수 없기 때문에
  • 가상 머신은 클래스 파일을 로드할 때 상수 풀에서 해당 심벌 참조들을 가져 온다.
  • 그 후 클래스 타입의 인스턴스가 생성되거나 구동할 때 해석하여 실제 메모리 주소를 변환한다.
  • 상수 풀 안의 상수 각각은 모두 테이블이다.
  • JDK 21 기준 총 17가지 상수 타입이 존재한다.
  • 17가지 타입의 테이블들은 공통적으로 u1타입의 플래그 비트로 시작하며, 그 값은 현재 상수가 속한 상수 타입을 나타낸다.
  • 17가지 상수 타입 각각의 데이터 구조는 독립적이다.

상수 풀의 항목 타입

상수풀 항목 이름 Tag 값 주요 용도 데이터 구조/포함 정보
CONSTANT_Utf8_info 1 문자열 저장 (메서드명, 필드명 등) 길이 + UTF-8 바이트
CONSTANT_Integer_info 3 int 상수 값 4바이트 정수 값
CONSTANT_Float_info 4 float 상수 값 4바이트 IEEE 754 float 값
CONSTANT_Long_info 5 long 상수 값 8바이트 정수 값
CONSTANT_Double_info 6 double 상수 값 8바이트 IEEE 754 double 값
CONSTANT_Class_info 7 클래스/인터페이스 이름 참조 이름에 대한 Utf8 상수풀 인덱스
CONSTANT_String_info 8 String 리터럴 참조 Utf8 상수풀 인덱스
CONSTANT_Fieldref_info 9 필드 참조 Class 인덱스 + NameAndType 인덱스
CONSTANT_Methodref_info 10 메서드 참조 Class 인덱스 + NameAndType 인덱스
CONSTANT_InterfaceMethodref_info 11 인터페이스 메서드 참조 Class 인덱스 + NameAndType 인덱스
CONSTANT_NameAndType_info 12 이름과 타입 서명 정보 이름 인덱스 + 타입 서명 인덱스
CONSTANT_MethodHandle_info 15 메서드 핸들 (invoke dynamic 관련) 참조 kind + 참조 index
CONSTANT_MethodType_info 16 메서드 타입 정보 Descriptor(Utf8) 인덱스
CONSTANT_Dynamic_info 17 동적 상수 (런타임 시점에 결정) Bootstrap method index + NameAndType 인덱스
CONSTANT_InvokeDynamic_info 18 동적 메서드 호출 Bootstrap method index + NameAndType 인덱스
CONSTANT_Module_info 19 모듈 이름 Utf8 인덱스
CONSTANT_Package_info 20 패키지 이름 Utf8 인덱스

  • TestClass의 첫 번째 상수의 플래그 비트는 0x0A이다. 십진수로 10
  • 상수 풀의 항목 타입에서 플래그 값이 10인 상수 타입은 CONSTANT_Methodref_info이다.
  • 이 타입은 '같은 클래스의 메서드'를 가리키는 심벌 참조다.
  • CONSTANT_Methodref_info의 구조
CONSTANT_Methodref_info {
    u1 tag;
    u2 index;
    u2 index;
}
  • TestClass의 첫 번째 상수의 첫 번째 인덱스와 두 번째 인덱스 모두 상수 풀에서의 인덱스로, 값이 각각 2와 3이다.
  • 상수 풀에서의 두 번째 상수와 세 번째 상수를 확인해봐야 한다.
  • 두 번째 상수의 플래그 비트(오프셋 주소: 0x0000000F)는 07이므로 상수 타입은 CONSTANT_Class_info이다.
  • 이 타입은 '클래스나 인터페이스를 가리키는 심벌 참조'
  • CONSTANT_Class_info의 구조
CONSTANT_Class_info {
    u1 tag;
    u2 name_index; // 0004
}

  • 세 번째 상수의 플래그 비트(오프셋 주소: 0x00000012)는 0C이므로 상수 타입은 CONSTANT_NameAndType_info이다.
  • 이 타입은 '메서드의 이름과 타입을 가리키는 심벌 참조'
  • CONSTANT_NameAndType_info의 구조
CONSTANT_NameAndType_info{
    u1 tag;
    u2 index; // 0005
    u2 index; // 0006
}

  • 상수 풀에서 두 번째 상수의 name_index의 값은 04이므로 상수 풀에서의 네 번째 상수를 보면 이 메서드가 정의된 클래스의 이름을 알 수 있게 된다.
  • 이런 식으로 하나씩 추적해 조합하면 첫 번째 상수의 의미는 Object 클래스의 기본 인스턴스 생성자임을 알게 된다.
  • 추적하는 과정에서 클래스의 이름(상수 풀에서 네 번째 상수 데이터 구조 안), 메서드 이름(상수 풀에서 다섯 번째 상수 데이터 구조 안), 메서드 타입(상수 풀에서 여섯 번째 상수 데이터 구조 안) 정보는 모두 플래그 비트가 0x01로, UTF-8로 인코딩된 문자열로 표현된다.
  • 플래그 비트가 01에 해당되는 상수 타입은 CONSTANT_Utf8_info이다.
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length]
}

  • 클래스 이름이 되는 문자열의 길이(오프셋 주소: 0x00000018)는 0x0010, 즉 16바이트다. 이어지는 16바이트는 모두 아스키코드로 1~127 범위에 있으며, 값은 "java/lang/Object"다.
  • JDK의 bin 디렉터리 안의 javap 실행 파일은 클래스 파일의 바이트코드 분석 도구다.

$ javap -verbose TestClass
...
Compiled from "TestClass.java"
public class io.dev.TestClass
  minor version: 0
  major version: 61
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #8                          // io/dev/TestClass
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 1, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Fieldref           #8.#9          // io/dev/TestClass.m:I
   #8 = Class              #10            // io/dev/TestClass
   #9 = NameAndType        #11:#12        // m:I
  #10 = Utf8               io/dev/TestClass
  #11 = Utf8               m
  #12 = Utf8               I
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               LocalVariableTable
  #16 = Utf8               this
  #17 = Utf8               Lio/dev/TestClass;
  #18 = Utf8               inc
  #19 = Utf8               ()I
  #20 = Utf8               SourceFile
  #21 = Utf8               TestClass.java
{
  public io.dev.TestClass();
    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 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lio/dev/TestClass;

  public int inc();
    descriptor: ()I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #7                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lio/dev/TestClass;
}

상수 풀의 17개 데이터 타입의 일반적인 구조

항목 이름 Tag 필드명 타입 필드 설명
CONSTANT_Utf8_info 1 length u2 UTF-8 인코딩된 문자열의 바이트 수
    bytes u1[] UTF-8 인코딩된 문자열 내용(길이=length)
CONSTANT_Integer_info 3 bytes u4 int 값 (32비트 정수, 빅 엔디언)
CONSTANT_Float_info 4 bytes u4 float 값 (IEEE 754 32비트 부동소수, 빅 엔디언)
CONSTANT_Long_info 5 high_bytes u4 상위 32비트 정수
    low_bytes u4 하위 32비트 정수
CONSTANT_Double_info 6 high_bytes u4 상위 32비트 부동소수
    low_bytes u4 하위 32비트 부동소수
CONSTANT_Class_info 7 name_index u2 완전한 이름의 상수 항목의 인덱스
CONSTANT_String_info 8 string_index u2 문자열 리터럴의 인덱스
CONSTANT_Fieldref_info 9 class_index u2 선언된 필드의 클래스나 인터페이스의 서술자인 CONSTANT_Class_info의 인덱스
    name_and_type_index u2 필드 서술자인 CONSTANT_NameAndType_info의 인덱스
CONSTANT_Methodref_info 10 class_index u2 메서드가 선언된 클래스의 서술자인 CONSTANT_Class_info의 인덱스
    name_and_type_index u2 이름 및 타입 서술자인 CONSTANT_NameAndType_info의 인덱스
CONSTANT_InterfaceMethodref_info 11 class_index u2 메서드가 선언된 클래스의 서술자인 CONSTANT_Class_info의 인덱스
    name_and_type_index u2 이름 및 타입 서술자인 CONSTANT_NameAndType_info의 인덱스
CONSTANT_NameAndType_info 12 name_index u2 필드 또는 메서드 이름 상수 항목의 인덱스
    descriptor_index u2 필드 또는 메서드 서술자 상수 항목의 인덱스
CONSTANT_MethodHandle_info 15 reference_kind u1 메서드 핸들 타입(1~9), 메서드 핸들의 바이트코드 동작을 나타냄
    reference_index u2 상수 풀에서 유효한 인덱스 값
CONSTANT_MethodType_info 16 descriptor_index u2 상수 풀에서 유효한 인덱스 값. 인덱스가 가리키는 항목은 메서드의 서술자를 표현하는 CONSTANT_Utf8_info여야 함.
CONSTANT_Dynamic_info_info 17 bootstrap_method_attr_index u2 부트스트랩 메서드 배열 인덱스
    name_and_type_index u2 상수 풀에서 유효한 인덱스 값. 인덱스가 가리키는 항목은 메서드 이름 및 메서드의 서술자를 표현하는 CONSTANT_NameAndType_info구조여야 함.
CONSTANT_InvokeDynamic_info 18 bootstrap_method_attr_index u2 현재 클래스 파일에 있는 부트 스트랩 메서드 테이블의 bootstrap_methods[] 배열에 대한 유효한 인덱스 값
    name_and_type_index u2 상수 풀에서 유효한 인덱스 값. 인덱스가 가리키는 항목은 모듈 이름을 나타내는CONSTANT_Utf8_info구조여야 함.
CONSTANT_Module_info 19 name_index u2 상수 풀에서 유효한 인덱스 값. 인덱스가 가리키는 항목은 모듈 이름을 나타내는CONSTANT_Utf8_info구조여야 함.
CONSTANT_Package_info 20 name_index u2 상수 풀에서 유효한 인덱스 값. 인덱스가 가리키는 항목은 패키지 이름을 나타내는CONSTANT_Utf8_info구조여야 함.
  • 상수 풀 다음의 2바이트는 현재 클래스(또는 인터페이스)의 접근 정보를 식별하는 접근 플래그다.
  • 현재 클래스 파일이 표현하는 대상이 클래스인지, 인터페이스인지, public인지, abstract인지, 클래스인 경우 final인지 등의 정보가 담긴다.
  • 클래스 파일에서 사용할 수 있는 접근 플래그의 종류와 의미
접근 플래그 이름 값 (16진수) 의미
ACC_PUBLIC 0x0001 클래스, 필드, 메서드가 public 접근 제어자를 가짐
ACC_PRIVATE 0x0002 필드, 메서드가 private 접근 제어자를 가짐 (클래스에는 사용되지 않음)
ACC_PROTECTED 0x0004 필드, 메서드가 protected 접근 제어자를 가짐 (클래스에는 사용되지 않음)
ACC_STATIC 0x0008 정적(static) 필드 또는 메서드
ACC_FINAL 0x0010 final 클래스(상속 불가), final 필드(값 변경 불가), final 메서드(오버라이드 불가)
ACC_SUPER 0x0020 invokespecial 바이트코드에서 특별한 의미로 사용 (JVM 내부용), invokesepecial이 어떤 의미인지 구별하려면 1.0.2 이후 JDK로 컴파일된 클래스에서는 이 플래그가 true여야 한다.
ACC_SYNCHRONIZED 0x0020 동기화된 메서드 (메서드에만 해당)
ACC_VOLATILE 0x0040 volatile 필드 (변경 감지용, 필드에만 해당)
ACC_BRIDGE 0x0040 브리지 메서드 (컴파일러가 생성한 메서드)
ACC_TRANSIENT 0x0080 transient 필드 (직렬화 제외, 필드에만 해당)
ACC_VARARGS 0x0080 가변 인자 메서드 (컴파일러에서 사용)
ACC_NATIVE 0x0100 네이티브 메서드 (Java가 아닌 외부 구현)
ACC_INTERFACE 0x0200 인터페이스 타입
ACC_ABSTRACT 0x0400 추상 클래스 또는 메서드
ACC_STRICT 0x0800 strictfp 메서드 또는 클래스 (부동소수점 연산 정확성 고정)
ACC_SYNTHETIC 0x1000 컴파일러가 자동 생성한 요소
ACC_ANNOTATION 0x2000 애너테이션 타입
ACC_ENUM 0x4000 열거형(enum) 타입
ACC_MODULE 0x8000 Java 9 이후의 module 선언을 나타냄 (클래스에만 해당)
  • access_flags 크기는 2바이트이므로 플래그 비트를 최대 16개 사용할 수 있다.
  • 정의되지 않은 플래그 비트의 값은 모두 0이어야 한다.
  • TestClass는 인터페이스, 열거형, 애너테이션, 모듈이 아닌 일반 자바 클래스이며, public 클래스이고, final과 abstract는 아니다.
  • JDK 1.2 이상을 사용했으므로 ACC_PUBLIC과 ACC_SUPER 플래그는 true여야 하고, 나머지 7개 플래그는 모두 false여야 한다.
  • access_flags의 값은 0x0001 | 0x0020 = 0x0021이 되어야 한다.

클래스 인덱스, 부모 클래스 인덱스, 인터페이스 인덱스

  • 이어서 현재 클래스 인덱스(this_class)와 부모 클래스 인덱스(super_class), 인터페이스 컬렉션(interfaces)가 나온다.
  • 이러한 정보는 파일의 상속 관계를 규정한다.
  • 앞의 두 인덱스는 u2 타입이며, 세 번째인 인터페이스 인덱스 컬렉션은 u2 타입 데이터들의 '묶음'이다.
  • 클래스 인덱스와 부모 클래스 인덱스는 각각 현재 클래스와 부모 클래스의 완전한 이름을 결정하는 데 쓰인다.
  • 자바 언어는 다중 상속을 허용하지 않으므로 부모 클래스 인덱스는 하나뿐이다.
  • 단, java.lang.Object만은 부모 클래스가 없다. 따라서 java.lang.Object를 제외한 모든 자바 클래스의 부모 클래스 인덱스는 값이 0이 아니게 된다.
  • 인터페이스 인덱스 컬렉션은 현재 클래스가 구현한 인터페이스들을 기술한다.
  • 컬렉션 내의 인터페이스 순서는 자바 코드에서 implements 키워드(현재 클래스 파일이 인터페이스를 표현한다면 extends 키워드) 뒤에 나열한 순서를 따른다.
  • 클래스 인덱스와 부모 클래스 인덱스 모두 u2 타입이며, 값은 CONSTANT_Class_info 타입의 클래스 서술자 상수를 가리킨다.
  • 클래스의 완전한 이름 문자열은 CONSTANT_Class_info 타입에 담긴 상수의 값을 인덱스로 하는 CONSTANT_Utf8_info 타입으로 정의된다.
  • 인터페이스 인덱스 컬렉션의 첫 항목은 u2 타입이며, 값은 인덱스 테이블의 크기를 뜻한다. 즉, 현재 클래스가 구현한 인터페이스의 수를 뜻한다.
  • 아무런 인터페이스도 구현하지 않았다면 값이 0이고 인터페이스 인덱스 컬렉션은 더 이상의 바이트를 차지하지 않고 곧바로 끝난다.

  • u2 타입값 세 개는 오프셋 주소 0x000000D7에서 시작한다.
  • 값은 각각, 0x0008, 0x0002, 0x0000이다.
  • 클래스 인덱스는 8, 부모 클래스 인덱스는 2, 인터페이스 인덱스 컬렉션의 크기는 0을 뜻한다.
#2 = Class              #4             // java/lang/Object
#3 = NameAndType        #5:#6          // "<init>":()V
#4 = Utf8               java/lang/Object
#5 = Utf8               <init>
#6 = Utf8               ()V
#7 = Fieldref           #8.#9          // io/dev/TestClass.m:I
#8 = Class              #10            // io/dev/TestClass
#9 = NameAndType        #11:#12        // m:I
#10 = Utf8              io/dev/TestClass

필드 테이블

  • 필드 테이블(field_info)은 인터페이스나 클래스 안에 선언된 변수들을 설명하는 데 쓰인다.
  • 자바 언어에서 필드란 클래스 변수와 인스턴스 변수를 뜻한다.=> 메서드 안에 선언된 지역 변수는 필드가 아니다.
  • 자바 언어에서 필드가 담을 수 있는 정보들
    • 필드에 접근할 수 있는 범위 제한(public, private, protected)
    • 인스턴스 변수와 클래스 변수의 구분(static)
    • 불변 여부(final), 휘발성(volatile, CPU 캐시가 아닌 메인 메모리를 직접 읽거나 쓰게 함)
    • 직렬화 시 포함 여부(transient)
    • 데이터 타입(기본 타입, 객체, 배열)
    • 필드 이름
  • 이들중 한정자 각각은 'true' 아니면 'false'로 설정 여부를 나타낼 수 있으니 플래그로 표현하기에 알맞다.
  • 반면 필드의 이름과 타입은 크기가 일정하지 않으니 상수 풀에 정의된 상수를 참조해 설명해야 하므로 필드 테이블의 최종 형태는 다음과 같다.
field_info {
    u2               access_flags;
    u2               name_index;
    u2               descriptor_index;
    u2               attributes_count;
    attribute_info   attributes[attributes_count];
}
  • 필드의 access_flags 항목이 가질 수 있는 값은 클래스의 access_flags와 매우 비슷하다.
접근 플래그 이름 값 (16진수) 의미
ACC_PUBLIC 0x0001 필드가 public (어디서든 접근 가능)
ACC_PRIVATE 0x0002 필드가 private (같은 클래스 내에서만 접근 가능)
ACC_PROTECTED 0x0004 필드가 protected (같은 패키지 또는 하위 클래스에서 접근 가능)
ACC_STATIC 0x0008 정적(static) 필드 (인스턴스 없이 사용 가능)
ACC_FINAL 0x0010 final 필드 (한 번만 값 설정 가능, 상수처럼 사용됨)
ACC_VOLATILE 0x0040 volatile 필드 (스레드 간 가시성 보장, 캐시 무효화)
ACC_TRANSIENT 0x0080 transient 필드 (직렬화에서 제외됨)
ACC_SYNTHETIC 0x1000 컴파일러에 의해 자동 생성된 필드 (소스 코드에 없음)
ACC_ENUM 0x4000 열거형(enum) 상수를 나타냄 (enum 클래스 내부 상수 필드에 사용됨)
  • access_flags 다음에는 name_index와 descriptor_index가 온다.
  • 이 둘은 상수 풀에서 인덱스로, 각각 '필드의 단순 이름'과 '필드 및 메서드 서술자' 참조를 가리킨다.
  • 완전한 이름, 단순 이름, 서술자
    • 현재 사용 중인 TestClass의 완전한 이름은 "io/dev/TestClass"이다.
    • 상수 풀에서 확인할 수 있다.
    • #7 = Fieldref #8.#9 // io/dev/TestClass.m:I #8 = Class #10 // io/dev/TestClass #9 = NameAndType #11:#12 // m:I #10 = Utf8 io/dev/TestClass
    • 단순 이름은 메서드나 필드의 이름을 참조할 때 이용하며 타입과 매개 변수 정보가 생략된 형태다.
    • TestClass의 inc 메서드와 m 필드의 단순 이름은 "inc"와 "m"이다.
    • public class TestClass { private int m; public int inc() { return m + 1; } }
    • 서술자의 역할은 필드의 경우엔 데이터 타입까지, 메서드의 경우엔 매개 변수 목록(개수, 타입, 순서 포함)과 반환값까지 기술하는 것이다.
    • 서술자에서 기본 데이터 타입(byte, char, double, float, int, short, boolean)과 void 타입(반환값 없음)은 대문자 하나를 쓰며
    • 객체 타입은 타입의 완전한 이름 앞에 "L"을 추가해 표현한다.
    • 배열 타입은 차원 수만큼 앞에 '['가 붙는다. 예를 들어 [Iint[], [[Ljava/lang/String;String[][]
  • 서술자 식별 문자
식별 문자 의미 (타입) 설명
B byte 1바이트 정수형
C char 2바이트 유니코드 문자
D double 8바이트 IEEE 754 부동소수점
F float 4바이트 IEEE 754 부동소수점
I int 4바이트 정수형
J long 8바이트 정수형
S short 2바이트 정수형
Z boolean 논리형 (true/false)
L<classname>; 참조형 객체(Object) 예: Ljava/lang/String;java.lang.String 객체
[<descriptor> 배열(Array) 예: [Iint[], [Ljava/lang/String;String[]
V void 메서드 서술자에서만 사용, 필드에는 없음
* 서술자가 메서드를 기술할 때는 매개 변수 목록을 먼저 적고 반환값을 적는다.    
* 매개 변수 목록은 "()" 안에 정확한 순서로 기술한다.    
* void inc() => "()V"    
* java.lang.String.toString() => "()Ljava/lang/String"    
* int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)=> "([CII[CIII)I"    
  • TestClas.class 파일의 필드 테이블 컬렉션은 오프셋 주소 0x000000BD부터 시작
  • 첫 번째 데이터는 필드 개수를 뜻하는 fields_count이며 타입은 u2다.
  • fields_count 값은 0x0001이므로 필드 테이블에는 데이터가 1개이다.

  • access_flags 값은 0x0002 => ACC_PRIVATE 플래그만 true이며, 나머지 플래그들은 모두 false임을 알 수 있다.
  • 실제로 TestClass의 필드가 m 하나이고 접근 지정자가 private임을 볼 수 있다.
public class TestClass {
    private int m;

    public int inc() {
        return m + 1;
    }
}
  • 다음의 name_index는 필드 이름을 가리키며 값이 0x000B로 11이다.
  • 따라서 상수 풀의 11번째 상수를 확인해보면, CONSTANT_Utf8_info 타입의 문자열이고 값은 "m"이다.
  • 이어서 필드 서술자인 descriptor_index의 값은 0x000C이고, 상수 풀에서 찾아보면 "I"다.
  • 서술자 식별 문자 I는 기본 타입 int를 뜻한다.
#10 = Utf8               io/dev/TestClass
#11 = Utf8               m
#12 = Utf8               I
#13 = Utf8               Code
  • 실제 TestClass의 필드 m의 데이터 타입은 int인 것을 볼 수 있다.
public class TestClass {
    private int m;

    public int inc() {
        return m + 1;
    }
}
  • 필드 테이블에 반드시 포함되어야 하는 데이터 항목은 descriptor_index까지다.
  • 이 뒤로는 몇 가지 추가 정보를 저장하는 속성 테이블 컬렉션이 따라온다.
  • 필드 테이블에는 속성 테이블에 0개 이상의 항목을 설명하는 0개 이상의 추가 정보를 덧붙일 수 있다.
  • 현재 TestClass 클래스 파일의 필드 테이블 컬렉션에서 필드 m의 경우 속성 테이블 개수가 0개이다. => 추가 설명이 필요없다는 것이다.
  • 부모 클래스나 부모 인터페이스로부터 상속받은 필드는 필드 테이블 컬렉션에 나열하지 않는다
  • 내부 클래스는 외부 클래스를 가리킬 수단이 필요한데, 이를 위해 컴파일러가 외부 클래스의 인스턴스를 가리키는 필드를 자동으로 추가한다.

메서드 테이블

  • 클래스 파일에서 메서드의 저장 형태는 필드 저장 형태와 거의 같다.
method_info {
    u2               access_flags;
    u2               name_index;
    u2               descriptor_index;
    u2               attributes_count;
    attribute_info   attributes[attribute_count];
}
  • 메서드 테이블 구조는 필드 테이블 구조와 완전히 같다.
  • 각 데이터의 항목의 의미도 필드 테이블 항목과 매우 비슷
  • 접근 플래그와 속성 테이블 컬렉션에서 선택할 수 있는 값만 살짝 다르다.
    • 메서드에는 volatile과 transient 키워드를 붙일 수 없으므로 메서드 테이블의 접근 플래그에는 ACC_VOLATILE과 ACC_TRANSIENT가 존재하지 않는다.
    • 반면 필드에는 없던 abstract, synchronized, native, strictfp 키워드에 대응하는 접근 플래그가 추가되었다.
  • 메서드 접근 플래그
접근 플래그 이름 값 (16진수) 의미
ACC_PUBLIC 0x0001 public 메서드 (어디서든 접근 가능)
ACC_PRIVATE 0x0002 private 메서드 (같은 클래스 내에서만 접근 가능)
ACC_PROTECTED 0x0004 protected 메서드 (같은 패키지 또는 하위 클래스에서 접근 가능)
ACC_STATIC 0x0008 정적(static) 메서드 (인스턴스 없이 호출 가능)
ACC_FINAL 0x0010 final 메서드 (오버라이드 금지)
ACC_SYNCHRONIZED 0x0020 동기화(synchronized) 메서드 (모니터 락 획득)
ACC_BRIDGE 0x0040 브리지(bridge) 메서드 (컴파일러가 제네릭을 처리하기 위해 생성)
ACC_VARARGS 0x0080 가변 인자(varargs) 메서드
ACC_NATIVE 0x0100 네이티브 메서드 (자바 외부의 C/C++ 코드와 연결됨)
ACC_ABSTRACT 0x0400 추상(abstract) 메서드 (본체 없음, 구현은 서브클래스가 담당)
ACC_STRICT 0x0800 strictfp 메서드 (부동소수점 연산을 IEEE 754 표준에 강제)
ACC_SYNTHETIC 0x1000 컴파일러가 자동 생성한 메서드 (소스 코드에 없음)
  • '메서드 선언'까지는 접근 플래그, 이름 인덱스, 서술자 인덱스만으로 명확하게 표현된다.
  • '메서드 본문의 코드', 자바 코드는 javac 컴파일러에 의해 바이트코드 명령어로 변환된 후, 메서드 속성 테이블 컬렉션의 "Code" 속성에 따로 저장된다.
  • 속성 테이블은 클래스 파일 형식에서 확장성이 가장 큰 데이터 항목이다.

  • 메서드 테이블 컬렉션의 시작 주소는 0x0000E7이다.
  • 첫 번째 u2 데이터 값, 즉 메서드 테이블의 항목 수는 0x0002로 2개이다.
  • 두 메서드의 정체는 소스 코드에 정의된 inc()와 컴파일러가 추가한 인스턴스 생성자인 <init>이다.
  • 첫 번째 메서드의 접근 플래그 값은 0x0001이므로 ACC_PUBLIC 플래그만 true다.
  • 이름 인덱스의 값은 0x0005이므로 TestClass 클래스 상수 풀의 5번째 상수를 확인해보면 메서드 이름은 <init>임을 확인할 수 있다.
  • 서술자 인덱스의 값은 0x0006이므로 마찬가지로 상수 풀의 6번째 상수를 확인해보면 "()V" 임을 확인할 수 있고 이는 TestClass의 생성자의 시그니처와 일치한다.
#4 = Utf8               java/lang/Object
#5 = Utf8               <init>
#6 = Utf8               ()V
#7 = Fieldref           #8.#9 
  • 속성 테이블 카운터 attributes_count의 값은 0x0001이므로 이 메서드의 속성 테이블 컬렉션에는 속성이 1개 뿐이다.
  • 속성 이름 인덱스의 값은 0x000D로, TestClass 클래스 상수 풀의 13번째 상수를 확인해보면 "Code"임을 확인할 수 있다. => 이 속성이 메서드의 바이트코드임을 나타낸다.
#11 = Utf8               m
#12 = Utf8               I
#13 = Utf8               Code
#14 = Utf8               LineNumberTable
  • 필드 테이블 컬레션과 마찬가지로 부모 클래스의 메서드를 오버라이딩하지 않았다면, 부모 클래스의 메서드 정보는 자식 클래스의 메서드 테이블 컬렉션에 나타나지 않는다.
  • 컴파일러는 자동으로 메서드를 추가할 수 있는데, 대표적인 예가 클래스 생성자인 <cinit>() 과 인스턴스 생성자인 <init>()이다.

속성 테이블

  • 속성 테이블 구조
attribute_info {
    u2             attribute_name_index;
    u4             attribute_length;
    u1             info[attribute_length];
}
  • 자바 가상 머신 명세에 사전 정의된 속성
속성 이름 (attribute_name) 사용 위치 의미
ConstantValue 필드 테이블 상수 필드에 대한 초기값을 나타냄 (static final 필드)
Code 메서드 테이블 바이트코드, 예외 테이블, 로컬 변수 정보 등을 포함하는 메서드의 실제 코드 블록
StackMapTable Code 바이트코드 검증에 사용되는 프레임 맵 정보 (JVM의 타입 안전성 체크용)
Exceptions 메서드 테이블 메서드가 throws 절을 통해 던질 수 있는 예외 목록
InnerClasses 클래스 파일 이 클래스에 정의된 모든 내부 클래스와 그 접근 정보를 기술
EnclosingMethod 클래스 파일 로컬 클래스나 익명 클래스의 외부 메서드 정보를 기술
Synthetic 클래스 파일, 필드 테이블, 메서드 테이블 소스 코드에는 없고 컴파일러가 생성한 요소임을 나타냄
Signature 클래스 파일, 필드 테이블, 메서드 테이블 제네릭 정보 등 서명(signature)을 나타냄 (소스 수준 정보)
SourceFile 클래스 파일 이 클래스가 정의된 .java 파일 이름을 나타냄
LineNumberTable Code 바이트코드 오프셋과 소스 코드 줄 번호의 매핑 (디버깅용)
LocalVariableTable Code 지역 변수의 이름, 타입, 범위 등을 명시 (디버깅용)
LocalVariableTypeTable Code 제네릭 지역 변수의 타입 정보 (디버깅용)
Deprecated 클래스 파일, 필드 테이블, 메서드 테이블 해당 요소가 사용 중단(deprecated)되었음을 나타냄
RuntimeVisibleAnnotations 클래스 파일, 필드 테이블, 메서드 테이블, Code, 매개변수 등 런타임에 유지되는 애너테이션 정보
RuntimeInvisibleAnnotations 클래스 파일, 필드 테이블, 메서드 테이블, Code, 매개변수 등 컴파일 시점까지만 유지되는 애너테이션 정보
RuntimeVisibleParameterAnnotations 메서드 테이블 메서드 매개변수에 대한 런타임 유지 애너테이션
RuntimeInvisibleParameterAnnotations 메서드 테이블 메서드 매개변수에 대한 컴파일 타임 애너테이션
RuntimeVisibleTypeAnnotations 클래스 파일, 필드 테이블, 메서드 테이블, Code 타입 애너테이션 (런타임 유지)
RuntimeInvisibleTypeAnnotations 클래스 파일, 필드 테이블, 메서드 테이블, Code 타입 애너테이션 (컴파일 타임 유지)
AnnotationDefault 메서드 테이블(애너테이션 인터페이스의 default 메서드) 애너테이션 메서드의 기본값
BootstrapMethods 클래스 파일 invokedynamic 바이트코드를 위한 부트스트랩 메서드 목록
MethodParameters 메서드 테이블 메서드 파라미터 이름과 속성 정보
Module 클래스 파일 모듈 정보를 담고 있는 속성 (Java 9+)
ModulePackages 클래스 파일 모듈에 포함된 패키지 목록 (Java 9+)
ModuleMainClass 클래스 파일 모듈의 main 클래스 (Java 9+)
NestHost 클래스 파일 이 클래스가 속한 nest의 호스트 클래스 (Java 11+)
NestMembers 클래스 파일 같은 nest에 속한 클래스들의 목록 (Java 11+)
Record 클래스 파일 레코드 클래스임을 나타내는 속성 (Java 14+)
PermittedSubclasses 클래스 파일 sealed 클래스의 허용된 하위 클래스 목록 (Java 15+)

Code 속성

Exception 속성