[Java] Javac의 동작 과정은 어떻게 될까?
문제상황
Java/Spring을 Kotlin/Spring으로 마이그레이션 하는 과정에서 Kotlin의 Lombok 사용에 어려움을 겪었다.
그리고 그 이유 Lombok이 Javac의 Annotation Processing 과정에서 처리되는데, Javac보다 Kotlinc가 더 먼저 작동하여 Javac에서 Annotation Processing 과정을 통해 생성되는 코드를 Kotlin 코드에서는 알 수가 없기 때문임을 알게 되었다.
이 과정에서 Javac의 Annotation Processing을 포함한 전반적인 과정을 흝어보고 싶어 학습하여 기록해 두게 되었다.
javac의 동작 과정
javac의 동작과정에 대해 자세히 파헤쳐보자.
javac는 대략 아래 7가지 단계로 컴파일을 수행한다.
1. Parsing: 소스 코드를 읽고 AST 생성.
2. Annotation Processing: 애노테이션 처리 및 코드 생성.
3. Semantic Analysis: 타입 검증과 의미 분석.
4. Intermediate Code Generation: 중간 코드 생성.
5. Optimization: 코드 최적화.
6. Bytecode Generation: 바이트코드 생성.
7. Class File Output: .class 파일 저장.
1. Parsing (구문 분석)
Javac는 입력받은. java파일의 소스 코드를 읽고, 이를 추상 구문 트리(Abstract Syntax Tree, AST)로 변환한다.
세부내용
• 토큰화(Tokenization): 코드의 각 요소를 토큰으로 나눈다. (ex. public class HelloWorld → [public] [class] [HelloWorld])
• 구문 분석(Syntax Analysis): 토큰을 기반으로 코드 구조를 파악하고, AST를 생성한다.
• 이 단계에서 구문 오류(Syntax Error)를 발견하면 컴파일이 중단된다.
2. Annotation Processing (애노테이션 처리)
@Override, @Deprecated, 또는 사용자 정의 애노테이션(예: Lombok) 등을 처리한다.
세부내용
• Annotation Processor가 등록된 애노테이션에 대해 지정된 작업을 수행한다.
• Lombok 라이브러리는 이 단계에서 보일러플레이트 코드를 생성한다.
• 출력: 수정된 AST 또는 추가 생성된 파일(예: 자동 생성된 클래스).
3. Semantic Analysis (의미 분석)
코드의 의미를 검증한다.
세부내용
• 변수나 메서드가 선언된 위치와 호출 위치를 연결한다.
• 타입 체크(Type Checking): 변수와 메서드 호출이 올바른 타입으로 사용되는지 확인한다.
• 이 단계에서 발견된 오류는 컴파일 에러로 표시된다.
4. Intermediate Code Generation (중간 코드 생성)
AST를 바탕으로 JVM이 이해할 수 있는 중간 표현(Intermediate Representation, IR)을 생성한다.
세부내용
• 이 단계는 소스 코드를 바이트코드로 변환하기 위한 중간 단계로, 구체적인 JVM 명령어로 변환되지 않았지만 구조가 더 단순화된 상태이다.
5. Optimization (최적화)
생성된 중간 코드를 최적화한다.
세부내용
• 중복된 연산 제거.
• 불필요한 코드 제거.
• 더 효율적인 명령어로 대체.
• 이 단계는 JVM의 Just-In-Time(JIT) 컴파일러 최적화보다는 덜 강력하지만, 일부 성능 최적화를 수행한다.
6. Bytecode Generation (바이트코드 생성)
최적화된 중간 코드를 JVM에서 실행될 수 있는 바이트코드(.class 파일)로 변환한다.
7. Class File Output (클래스 파일 출력)
최종적으로 생성된 .class 파일을 디스크에 저장한다.