본문 바로가기
Language/Java

☕Java의 기본 개념과 컴파일 메커니즘

by 박수무당벌레 2026. 6. 14.

🔗 타입 검사 & 타입 소거 (제네릭)

 

1. 자바 코드와 개념 이해

1) 개념 한 줄 요약

  • 클래스 (Class): 무엇인가를 만들기 위한 설계도 (실체가 없음)
  • 객체 (Object): 그 설계도를 보고 진짜로 만들어낸 실체 (메모리에 존재함)
  • 메서드 시그니처 (Method Signature): 메서드의 이름과 매개변수의 리스트(타입, 개수, 순서)의 조합. 자바는 리턴 타입이나 변수 이름은 구별하지 않고 오직 이 시그니처로만 메서드를 구분
    • 예: sum(int, int)
  • 값의 반환: 메서드가 호출되면 내부 로직을 수행한 후, return 키워드를 통해 자신이 종료되면서 호출자(Caller)에게 결과값을 제공.

2) 메인 메서드 (static main)의 비밀

  • 필수 조건: JVM(자바 가상 머신)이 프로그램을 실행하고 호출할 수 있도록 main 메서드가 반드시 존재해야 한다.
  • static의 의미: 원래 설계도(Class)로 객체(인스턴스)를 생성해야 내부 함수를 쓸 수 있지만, static이 붙은 클래스 메서드 객체 생성 없이도 클래스 레벨에서 바로 사용 가능
  • 이유: 프로그램이 시작될 때 아직 객체가 만들어지지 않은 상태이므로, JVM에 내 자바 코드의 시작 지점(Entry Point)을 알려주는 유일한 방법이기 때문
public class Main {
    public static void main(String[] args) {
        // 프로그램 실행 진입점 (Entry Point)
    }
}
STS4에서 main에 전달될 argument 지정: 코드 창 우클릭 > Run As > Run Configurations 로 설정 팝업 > Java Application 메뉴 > Arguments 탭/Program Arguments 에서 지정 가능 (공백을 구분자로 받음) / 최소 0개

3) 패키지와 FQCN (Fully Qualified Class Name)

  • 패키지(Package)란?: 관련된 클래스들을 모아둔 '폴더' 같은 물리적 묶음 (파일 관리 시스템에 가까움)
    • 접근 제어자 중 default와 protected가 말하는 '같은 패키지'는 '같은 폴더 안'에 존재한다는 뜻
  • FQCN이란? 자바가 인증하는 클래스의 '전체 소속 패키지 경로를 포함한 공식 이름' (namespace와 역할 유사) 
    • 사용 목적: ① 다른 사람이 만든 클래스나 라이브러리와 내 클래스간 동명의 클래스 구분(네임스페이스 분리하여 이름 충돌 방지), ② 숏네임만으로 알 수 없는 클래스의 정확한 소속 알림
    • 예: Main 클래스의 FQCN은 com.example.myproject.Main (보통은 간소화해서 Main 클래스)
  • Package Presentation 설정: STS 패키지 탐색기 구조를 보기 쉽게 Hierarchical(계층형)로 설정
⚠️ 주의사항: 자바는 패키지명에 맞춰 실제 컴퓨터의 디렉토리(폴더) 구조를 구성하므로, 패키지 구조와 클래스명이 실제 파일 경로와 다르면 에러가 발생함

💡 참고 (String.class와 리버스 엔지니어링) 우리가 자주 쓰는 String 클래스는 JDK 내부의 java.lang.String 패키지에 위치한다. 컴파일된 바이트코드(.class)를 리버스 엔지니어링(역공학)하면 원래의 자바 소스 코드로 복원하여 내부를 확인할 수 있음.


2. 자바 가상 머신(JVM)의 실행 환경 및 흐름

1) 빌드 메커니즘 

  1. 소스 파일 생성: 클래스 이름과 정확히 일치하는 '클래스명.java' 파일을 생성하고 코드를 작성한다.
  2. 컴파일 및 로드: STS4가 코드를 빌드하면 목적 파일인 '클래스명.class' (*바이트코드) 파일이 생성 (주로 '현재 디렉토리/bin/...' 하위에 생성됨)
    • **컴파일: ① 문법 오류 검사, ② 컴파일러(javac)가 .java 파일을 기계어에 가까운 .class 파일(바이트코드)로 변환 (타입 소거 등) 
    • JAR 생성 타임(패키징): 컴파일이 끝나서 .class 파일들로 변환되면, 빌드 도구(Gradle, Maven 등)나 개발자가 이 파일들과 패키지 디렉터리를 묶어서 하나의 ~.jar 파일로 압축
더보기

 컴파일 단계에서 일어나는 핵심 작업

 

1) 문법 검사 및 추상 구문 트리(AST) 생성

  • 컴파일러가 가장 먼저 하는 일은 코드가 자바 문법에 맞게 작성되었는지 검사하는 것 (문장의 구조(Format)를 보는 단계)
  • 어휘 분석 (Lexical Analysis): 소스 코드를 글자 단위로 읽어서 의미 있는 최소 단위인 토근(Token)으로 쪼갬.
    • 예: int a = 10;을 int, a, =, 10, ;로 분리
  • 구문 분석 및 문법 검사 (Syntax Analysis/Parsing): 쪼갠 토큰들이 자바 문법 규칙에 맞게 배열되었는지 검사하며, 이 과정에서 파스 트리(Parse Tree)라는 구조를 만듦. (AST를 만드는 역할)
    • 파스 트리(문법 트리): 문법 검사를 위함. 문법 검사에 통과하면 소스 코드의 모든 문법적 요소(괄호, 세미콜론, 키워드 등)를 문자 그대로 전부 포함해서 트리로 만듦. 문법 규칙을 완벽하게 반영하기 때문에 구조가 매우 복잡하고 큼.
    • 예: 세미콜론(;)이 빠졌거나 괄호 짝이 안 맞으면 → 컴파일 error 발생
  • 추상 구문 트리(AST) 구축: 문법 검사가 끝나면 파스 트리에서 불필요한 기호를 빼고 코드를 컴퓨터가 해석하기 좋은 트리 구조(AST)로 재구성. 이 트리를 바탕으로 이후의 모든 검사와 변환이 이루어짐.
    • 요약: 연산자와 피연산자의 관계만 남김 → 소스 코드 분석/해석(최적화 및 변환)에 유리

2) 타입 체크 및 상속 구조 확정 (Semantic Analysis: 의미 분석)

  • OOP 뼈대 완성과 검증 (구조는 맞는데 말이 되는지(Meaning)를 보는 단계)
    • AST 바탕으로 변수의 타입(int)과 대입하려는 값의 타입(String)이 맞지 않는다는 '의미론적 오류'를 잡아냄
    • 예: 잘못된 타입간 연산, 선언되지 않은 변수를 썼는지, 상속 관계가 없는 클래스로 캐스팅을 시도했는지 등
  • 메모리 설계도(클래스 레이아웃) 완성: 객체의 메모리 구조(크기와 배치)를 미리 확정
  • 타입 체크 (Type Checking): 다형성 코드가 있을 때, 실제로 상속 관계가 맞는지 확인하여 안전성을 검사. 데이터 타입이 맞지 않는 대입(예: String s = 10;)도 걸러냄

3) 코드 보완 및 자동 변환 (편의 기능 삽입)

  • 디폴트 생성자 삽입: 클래스에 생성자가 하나도 없다면, 컴파일러가 아무 내용도 없는 디폴트 생성자를 자동으로 넣어줌.
  • 오토박싱 / 언박싱 변환 
    • 오토박싱 (AutoBoxing): 데이터 타입의 변환 (기본 타입 → 참조 타입). Integer.valueOf() 같은 코드를 대신 채워넣어줌. (valueOf 메서드가 호출되어 실제 객체가 생성되거나 캐시된 객체를 가져오는 동작은 런타임에서)
  • 문자열 최적화: String s = "A" + "B"; 같은 코드가 있다면, 컴파일러가 미리 "AB"로 합쳐두거나 StringBuilder를 사용하는 코드로 자동 변환 (더하기 연산을 StringBuilder로 바꾸기).
  • 타입 정보 제거: <String> 같은 제네릭 정보를 지우고, 일반 Object나 제한된 타입(Bounded Type, 예: Comparable)으로 변경
  • 형변환(Casting) 코드 자동 삽입: 제네릭이 사라지면 값을 꺼낼 때 Object로 나오기 때문에, 컴파일러가 소스 코드에 (String)처럼 안전한 형변환 코드를 자동으로 삽입해 줌.

4) 중간 코드 생성 및 최적화

  • 다듬어진 추상구문 트리(AST)를 바탕으로 intermediate representation(IR)이라는 중간 코드를 만듦.
  • 이 단계에서 컴파일러는 실행 속도를 높이기 위해 최적화 작업을 수행한다
    • 예: 반복문 안에 안 변하는 값 → 밖으로 뺌 / 쓸데없는 반복문 없애기, 안 쓰는 변수 제거하기 등
  • 왜 중간 코드(IR)을 만들까?
    • ① 컴파일러의 가성비를 높임: 프로그래밍 언어마다 바이트 코드 번역기가 필요하게 됨(M × N). 중간에 규격화된 중간 코드를 두면 구조가 단순해짐
    • ② 최적화 쉬움: 컴퓨터가 분석하기 좋게 '그래프나 삼소소 코드(Three-Address Code)' 형태로 존재하기 때문에, 컴파일러가 대수학적인 공식이나 알고리즘을 적용해서 코드를 최적화하기 가장 좋은 상태가 됨
      • 소스 코드 상태에서 최적화 하려면? 인간의 언어와 너무 가까워 컴퓨터가 구조를 분석하며 최적화하기가 너무 복잡함
      • 바이트 코드 상태에서 최적화 하려면? 코드가 너무 잘게 쪼개져 있고 단순해져서, 전체적인 맥락(흐름)을 파악하고 최적화하기가 어려움
💡코드의 종류
소스 코드: 특정 언어 문법에 종속되어 사람이 읽기 쉬움 (.java, .cpp 파일 형태)
중간 코드(IR): 컴파일러가 읽고 다루기 쉬움. (컴파일러 내부 메모리 상의 데이터로 존재)
바이트 코드: 가상 머신이 빠르게 읽고 실행하는 데 최적화된 구조(대개 스택 기반의 아주 단순한 명령어들)임. (.class 등의 실행파일)
ℹ️ 삼주소 코드(3AC) 를 거치는 이유
삼주소 코드(3AC)는 한 줄에 최대 3개의 변수/상수(결과 포함)만 사용하도록 제한된 코드 형태로, 복잡한 연산을 쪼개기 위해 임시 변수(임시 레지스터)를 사용한다
 실제 CPU의 동작과 유사: 실제 물리적인 CPU는 'ADD R1, R2, R3' (R2와 R3를 더해서 R1에 넣으라) 같은 방식으로 일함. 실제 하드웨어의 작동 방식과 논리적 구조가 똑같기 때문에, 나중에 최종 기계어로 바꿀 때 번역하기 편리
 최적화: 컴파일러가 중복 연산 같은 비효율적인 코드를 찾아내기 쉬움
💡바이트 코드도 JVM이 읽는 일종의 중간 코드(javac 기준)이다. 자바 환경에서는 '코드 보완 및 자동 변환 -> 바이트 코드 생성 단계'로 이어지며, 본격적인 실행 효율을 위한 최적화는 컴파일 단계뿐만 아니라 프로그램을 실행할 때 JIT 컴파일러가 바이트 코드를 기계어로 바꾸는 과정에서 아주 강력하게 일어남.

 

5) 바이트코드 생성 (최종 결과물)

  • 위의 검사와 변환이 끝나면, JVM(자바 가상 머신)이 실행할 수 있는 최종 명령어 세트(바이트코드)로 변환(번역)
  • .class 파일 생성: 우리가 작성한 .java 파일이 JVM의 기계어인 바이트코드(.class)로 출력
  • 바이트코드 안에는 클래스의 구조, 메서드의 명령어, 그리고 "상속 관계 및 타입 정보"가 고스란히 담긴 메타데이터(콘스턴트 풀 등)가 포함

💡 한눈에 보는 컴파일러의 역할 컴파일러는 철저한 문법 및 타입 감시자이자, 런타임에 프로그램이 바로 실행될 수 있도록 완벽한 설계도를 미리 짜두는 역할을 함. 컴파일 단계가 끝나면, 프로그램의 구조적인 형태는 100% 확정

💡컴파일러가 처음부터 Intel CPU용 기계어로 변환해 버린다면? 그 파일은 M1 맥북이나 스마트폰에서는 실행할 수 없다! 그래서 컴파일러는 특정 하드웨어에 종속되지 않는 중립적인 최종 명령어 세트(바이트 코드)를 만들어 냄. 이후 각 운영체제(Windows, Mac, Linux)에 설치된 JVM이 이 바이트 코드를 받아서, 자기 컴퓨터 CPU가 진짜 알아듣는 '진짜 기계어'로 실시간 번역(JIT 컴파일)하며 실행하게 된다.
👀C/C++은 빌드 결과물로 바이트코드가 아닌 소스 코드를 컴퓨터가 바로 실행할 수 있는 기계어(.exe 등)로 번역
더보기

 JAR 파일 추가 설명

 

애플리케이션 실행 시 지정한 클래스패스(ClassPath) 및 모듈패스의 라이브러리. 자바 애플리케이션을 배포하고 실행하기 쉽게 여러 파일(바이트 코드, 이미지, XML 설정파일 등 리소스)들을 하나로 묶어놓은 자바 전용 압축 파일으로, 실제 구조는 일반적인 ZIP 포맷과 동일하다 (확장자를 .zip으로 바꾸면 일반 압축 프로그램으로도 내부를 열어볼 수 있음). 내부 구조와 목적에 따라 두가지로 나뉜다.

 

1) '빌드 결과물'로서의 JAR

  • 스스로 실행될 수 없고, 다른 프로그램의 특정 기능이나 화면 하나를 담당하는 파일.
  • 목적: 자주 쓰는 클래스나 기능들을 모아놓은 라이브러리(Library) 형태
  • 특징: 파일 안에 main() 메서드(프로그램의 시작점)가 없거나, 있더라도 이를 실행하기 위한 정보가 포함되어 있지 않음

2) '실행 파일'로서의 JAR (Executable JAR)

  • 윈도우의 .exe 파일이나 맥의 .app 파일처럼, 바로 프로그램을 구동할 수 있는 파일
  • 목적: 독립적으로 실행되어 하나의 온전한 프로그램(또는 서버)을 띄우는 형태
  • 특징:
    • 프로그램의 시작점인 public static void main(String[] args) 메서드가 반드시 존재
    • JAR 파일 내부의 META-INF/MANIFEST.MF라는 설정 파일 내부에 프로그램의 실행 시작점(시작 클래스)인 main() 메서드의 위치 정보가 포함되어 있음 (Main-Class: 주소 지정).
  • 실행 방법: 터미널(CMD)에서 java -jar 파일명.jar라는 명령어를 입력하면 프로그램이 바로 실행됨. 혹은 파일 더블 클릭

3) 윈도우(C언어) 프로그램과 비교

  C / C++ (윈도우) Java (자바) 역할
소스 코드 .c / .cpp .java 개발자가 사람이 읽을 수 있게 쓴 글
중간 부품 .obj (목적 파일) .class (바이트 코드) 컴파일러가 번역해 놓은 개별 부품 파일
최종 실행 파일 .exe 또는 .zip (실행 파일) .jar (실행 가능한 JAR) 위의 수십, 수백개의 부품 파일과 리소스를 하나로 묶은 실행 파일이자 압축 파일

💡한 줄 요약

.class 파일도 JVM 위에서 실행되는 파일은 맞지만, 부품 상태이기 때문에 이를 하나로 묶어서 사용자가 쓰기 편하게 만든 .jar 파일이 자바의 진짜 '최종 실행 파일'이자 '배포본'이다

 

4) 자바의 모듈 시스템 전환 전략

  JAR 파일은 본질적으로 클래스 파일들과 메타데이터를 모아놓은 ZIP 압축 파일일 뿐. 자바 9에서 모듈 시스템(Project Jigsaw)이 도입되기 전까지, 자바의 모든 라이브러리는 JAR(Java Archive) 형태로 배포되고 관리

  1. 하위 호환성 (목표)
    • 만약 모듈 시스템을 도입하면서 .mod나 .jmod 같은 완전히 새로운 확장자만 쓰도록 강제했다면 전 세계의 수많은 오픈소스 라이브러리와 빌드 도구(Maven, Gradle 등)를 한순간에 모두 수정 필요함
    • 기존 생태계 보호를 위해 기존에 잘 쓰던 JAR 포맷에 module-info.class(모듈 시스템에 필요한 정보)라는 명찰 하나만 달아서 모듈로 재사용(JAR 포맷 확장)
  2. '모듈 descriptor' 하나면 JAR도 모듈이 된다 (기술적 해결책)
    • 자바 구조상 JAR 파일 루트(root) 경로에 module-info.class라는 파일(모듈 descriptor) 단 하나만 포함되어 있으면, 자바 가상머신(JVM)은 이 JAR 파일을 완벽한 모듈(Explicit Module)로 인식
      • 클래스패스에 두면: 일반 라이브러리로 동작
      • 모듈패스(Modulepath)에 두면: 모듈로 동작
  3. 과도기를 위한 '자동 모듈(Automatic Module)' 지원 (예외 처리/배려)
    • module-info.class가 없는 기존의 평범한 JAR 파일이라도 모듈패스에 올리면, JVM이 JAR 파일의 이름을 기반으로 모듈 이름을 자동으로 만들어서 모듈처럼 쓸 수 있게 해줌
    • 기존의 JAR 형식을 그대로 유지함으로써, 개발자들과 빌드 도구들은 큰 혼란 없이 모듈 시스템으로 전환 가능. 

📊 자바의 클래스 검색 경로(의존성 관리 방식)

 

JVM에게 실행시 필요한 외부 라이브러리(.jar)나 클래스 파일들의 위치(경로)를 알려주기 위해 경로를 지정하는 두 가지 방식이 있음

  1.  클래스패스(Classpath): 전통적인 방식(~ 자바 8), 그냥 파일 단위로 나열된 경로 목록. JVM이 클래스패스에 지정된 폴더와 JAR 파일들이 하나의 공간에 있고(통째로 로드), 필요한 클래스를 앞에서부터 찾음
    • 단점① 폴더 방식은 보안상 취약점을 만들 수 있고, 어떤 클래스가 로드되는지 추적하기 어려움(서로 다른 라이브러리에 중복된 클래스 이름 사용), ② 프로그램 실행 전에 어떤 라이브러리가 누락되었는지 알 수 없어, 실제 그 기능을 실행하는 순간에 에러(NoClassDefFoundError)가 남. (혹은 내가 원치 않는 클래스까지 강제 로드되어 클래스 간 충돌)
  2. 모듈패스(Modulepath): 현대적인 방식(자바 9 ~)으로 '모듈 단위'로 구조화(= Project Jigsaw)된 경로 목록. 각각을 독립된 모듈으로 격리(Java 플랫폼 자체를 모듈화)해서 자바 런타임 이미지(jmod 등) 내에 엄격하게 정의된 표준 플랫폼 모듈 중 일부를 로드.
    • 각 모듈 앞에 "나는 A, B 기능만 외부에 공개할 거고, 내 기능을 쓰려면 C 라이브러리가 필요해"라는 명찰(module-info.class)을 붙임 (필요한 파일들이 서로 어떤 연관 관계를 맺고 있는지 규칙으로 연결)
    • 장점① 내부 코드 숨김(철저한 은닉, 보안성 향상),  프로그램을 켜는 순간, 필요한 모듈이 다 있는지 JVM이 실행 전 전수조사 (하나라도 없으면 실행조차 되지 않고 즉시 에러)
  클래스패스 (Classpath) 모듈패스 (Modulepath)
도입 시기 자바 1 ~ 자바 8 (전통적) 자바 9부터 (현대적)
관리 단위 개별 클래스, 단순 JAR 파일 모듈 (명찰을 가진 JAR)
접근 제어 public 클래스면 누구나 접근 가능 public이라도 모듈이 허용(exports)해야만 접근 가능
안정성 실행 중에 라이브러리가 없어서 깨질 수 있음 실행 직후 모듈 관계를 체크하여 없으면 바로 에러
💡 참고: .jmod 파일은 뭔가요? JDK 내부를 보면 .jmod라는 새로운 확장자가 있음. 이는 자바 런타임 자체를 모듈화하기 위해 만든 새로운 포맷이 맞지만 jmod 파일은 네이티브 코드나 설정 파일 등 JAR보다 더 다양한 것을 담을 수 있는 대신 실행 시점에는 사용할 수 없고 오직 빌드/링크 시점에만 사용 가능. 그래서 일반 라이브러리는 여전히 JAR를 사용한다.
ℹ️ 여전히 하위 호환성 때문에 클래스패스를 많이 섞어 쓰지만, 자바 플랫폼 자체와 대형 최신 프레임워크들은 점점 모듈패스(모듈 시스템)를 적극적으로 활용하는 추세
ℹ️ 외부 라이브러리: 다른 개발자나 회사(예: 구글, 아파치 등)가 미리 만들어 둔 재사용 가능한 자바 코드(클래스들과 기능)의 묶음.(보통 .jar 파일 형태로 배포됨.) 데이터베이스 연결, JSON 데이터 변환, 이메일 전송 같은 복잡한 기능을 이미 검증된 외부 라이브러리를 가져다 써서 개발 기간 감소

2) JVM 실행 메커니즘 

    1. JVM 실행(런타임 진입): JVM이 이 .class 파일을 자기 자신에게 로드
      • ① 운영체제(OS)의 JVM 프로세스 생성: 터미널에 'java -jar 파일이름.jar'(JAR  파일 실행) 혹은 'java 클래스이름'(개별 클래스 파일 실행) 입력하거나 IDE에서 Run 버튼 클릭 ➡️ OS가 실행 프로그램을 메모리에 올릴 때, JVM이 사용할 메모리 공간 (Runtime Data Area)이 통째로 할당됨
      • ② 클래스 패스(Classpath)에 지정된 디렉토리(또는 JAR 파일 그 자체의 루트 경로) 확인: 아래를 수행하기 위해 가장 먼저 기준이 되는 루트(Root) 경로를 확인하는 런타임 초기화 과정. (예: *JAR 파일)
        • JAR 파일을 실행할 때는 JVM이 시스템에 설정된 환경변수나 사용자가 입력한 -cp 옵션을 완전히 무시. 대신 오직 JAR 파일 내부의 기준 경로(클래스 패스)만 보고 경로를 찾음
        • JAR 내부 명세(Manifest) 확인: JVM이 깨어나서 ~.jar 내부에 있는 'META-INF/MANIFEST.MF' 파일을 열고, 실행 기준이 되는 메인 클래스 위치(Main-Class)와 자체 Class-Path 경로(추가로 읽어야 할 외부 JAR나 폴더 경로)를 확인.
        • -cp 옵션 확인(예: java -cp . Hello): 자바 실행 명령어로 일반 .class 파일을 직접 실행한 것. 클래스 패스를 현재 폴더(.)로 지정하여, 현재 폴더에서 Hello 클래스를 찾아 그 안에 있는 static void main 메서드를 실행하겠다는 의미. 생략하면 기본값이 .(현재 폴더)이다
    • ③ 패키지 디렉터리 찾기 (최상위 클래스 로더 구동 및 탐색): JVM이 JAR 파일 내부(또는 명세에 적힌 경로)를 열어서 패키지 구조(폴더 경로)를 타고 들어감. (프로그램 시작을 위한 최소한의 필수 클래스들을 찾기 위해 실행되는 것)
      • JVM 자체를 구동하기 위한 핵심 클래스 탐색(부트스트랩 클래스 로더): 정해진 기본 경로 'JDK 설치경로/jmods' 폴더 내부에 있는 'java.base.jmod' 같은 시스템 모듈 파일들을 찾아 들어감. 내부 경로 도달 후 패키지 구조를 타고 탐색
      • 시작점(Main) 클래스 탐색: 사용자가 지정한 클래스 탐색
    • ④ .class 찾아서 로드: 최종 폴더에서 실행에 필요한 .class 파일을 찾아서 JVM 메모리(Runtime Data Area)에 올림(Load).
      • 파일을 메모리에 올린 직후, 내부적으로 바로 클래스 로더가 링크 과정(검증 ➡️ 준비 ➡️ 분석) 시작. 이후 초기화 단계 실행
  1. 진입점(Main 메서드) 실행: 번역된 프로그램을 JVM등의 실행 엔진이 실제로 구동하는 단계. 사용자가 지정한, JVM의 약속된 문법인 static main 메서드를 찾아 프로그램을 최종 실행
    • 동적 로딩: 특정 클래스가 필요해지면 JVM이 실시간으로 "패키지 디렉토리 찾기 ➡️ .class 로드"
💡실행 원리 요약
컴파일 단계: 소스코드(.java)를 저장하면 자바 컴파일러가 JVM이 이해할 수 있는 바이트코드(.class)로 변환
실행 단계: 컴파일러가 만든 바이트코드를 기반으로 실행되며, JVM이 실시간으로 컴퓨터가 이해할 수 있는 기계어로 번역하며 실행(JIT 컴파일 등)
ℹ️ JVM이 구동될 때 가장 먼저 기준이 되는 기본 루트(주로 개발자가 설정한-classpath나 JAR 내부의 경로)를 확인하는 주체는 시스템 클래스 로더(System/Application Class Loader). 개발자가 직접 작성한 클래스나 외부 라이브러리(.jar)들을 로드하기 위한 준비 과정이다.
더보기

 JVM 바이트 코드 파싱 시점

 

1) 클래스 로딩 시점의 파싱 (구조적 파싱)

JVM의 내부 서브시스템인 클래스 로더(Class Loader)는 코드 동작 중에 특정 클래스가 필요하면, 해당 .class 파일(바이트코드)을 읽어 메모리(Runtime Data Area)에 읽어 들이면서(Load) 첫 번째 파싱이 일어남. 위의 [JVM 실행] 단계에 해당

  • 무엇을 파싱하나: .class 파일의 바이너리 구조를 파싱. JVM 명세(Specification)에 맞게 파일이 구성되어 있는지, 자바 버전은 호환되는지, 클래스 이름, 부모 클래스, 메서드와 변수의 정보(상수 풀, Constant Pool) 등을 분석한다.
  • 목적: 파일 형태의 바이트코드를 JVM이 관리할 수 있는 메모리 상의 데이터 구조로 변환하기 위함

2) 실행 시점의 파싱 및 변환 (명령어 파싱)

 main 메서드를 찾아 실행하게 되면, JVM 내부의 실행 엔진(Execution Engine)이 바이트코드의 실제 "명령어 Opcode)"들을 한 줄씩 파싱하며 실행함. 이때 두 가지 방식으로 파싱과 변환이 일어남. 위의 [진입점 실행] 단계 및 프로그램이 동작하는 과정에 해당 

  1. 인터프리터(Interpreter)의 파싱:
    • main 메서드가 시작되면, 인터프리터가 바이트코드 명령어를 한 줄씩 읽고(파싱), 컴퓨터(CPU)가 이해할 수 있는 기계어로 번역하여 즉시 실행
    • 가령 바이트코드의 iload_1(변수 로드), iadd(더하기) 같은 명령어를 파싱하여 실제 CPU 명령어로 매핑하는 과정임.
  2. JIT(Just-In-Time) 컴파일러의 파싱:
    • 프로그램이 실행되면서 자주 호출되는 메서드나 반복문(Hot 코드)이 발견되면, JIT 컴파일러가 등판
    • JIT 컴파일러는 해당 바이트코드 블록을 통째로 파싱하여 분석한 뒤, 아주 최적화된 기계어로 한 번에 컴파일해 버림. 이후에는 파싱 과정 없이 이 기계어를 바로 실행하므로 속도가 빨라짐

💡 한 줄 요약

.class 파일의 껍데기와 구조를 파싱하는 것은 [JVM 실행(클래스 로드)] 단계,

.class 파일 내부의 실행 명령어(코드)를 파싱하는 것은 [진입점 실행(인터프리터/JIT 실행)] 단계