기본 이해
- 정의: 자바 소스코드(.java)를 JVM이 실행할 수 있는 바이트코드(.class)로 변환하고 실행하는 과정
- 비유: 외국어 번역과 실행
- 소스코드 = 원어
- 컴파일 = 번역
- 바이트코드 = 중간언어
- JVM = 각 나라의 통역사
- 작동방식:
- 소스코드 작성 (.java)
- 컴파일러가 바이트코드로 변환 (.class)
- JVM이 바이트코드를 실행
핵심 구성요소
자바 컴파일러(javac)
- 소스코드를 바이트코드로 변환
- 문법 검사 수행
JVM(Java Virtual Machine)
- 바이트코드를 실행
- 플랫폼 독립성 제공
클래스로더
- 필요한 클래스를 메모리에 로드
JIT 컴파일러
- 자주 사용되는 코드를 기계어로 변환
존재 이유와 목적
- 플랫폼 독립성 확보
- 타입 안정성 보장
- 성능 최적화 가능
- 메모리 관리 자동화
주의사항과 일반적 오류
- 컴파일 에러
- 문법 오류
- 타입 불일치
- 런타임 에러
- Null pointer exception
- Class not found exception
- 경로 설정 오류
- CLASSPATH 설정 실수
- JDK 설치 경로 문제
비교 분석
인터프리터 언어와 비교
- 컴파일 언어: C, C++
- 인터프리터 언어: Python, JavaScript
- 자바: 하이브리드 방식
장점
- 실행 속도가 빠름
- 타입 안정성
- 플랫폼 독립성
단점
- 개발-실행 사이 컴파일 필요
- JVM 설치 필요
- 메모리 사용량이 비교적 많음
실무 활용
// 소스코드 작성
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
// 컴파일
// javac Hello.java
// 실행
// java Hello
베스트 프랙티스
- IDE 활용 (Eclipse, IntelliJ)
- 빌드 도구 사용 (Maven, Gradle)
- 자동 빌드/배포 파이프라인 구축
관련 개념
- 이전 개념: 프로그래밍 기초, 운영체제 기본
- 후속 개념: JVM 아키텍처, 가비지 컬렉션, 클래스로더
추가 학습 포인트
- JVM 메모리 구조
- 가비지 컬렉션 동작 원리
- 클래스로더 계층구조
- JIT 컴파일러 최적화
추가 사항
자바 소스코드(.java)를 JVM이 실행할 수 있는 바이트코드(.class)로 변환하는 이유
프로그램을 다운로드 받다보면 아래와 같은 경우가 있었을 것이다.
특정 운영 체제 별로 다른 파일을 다운로드 받아야 하는 경우 말이다. 일반적인 경우 이렇게 각각의 os에 맞는 실행파일이 존재해야 하지만 java의 경우 바이트코드를 사용함으로써 이 과정을 생략할 수 있다.
이전에 설명한 JVM의 호환성과 관련이 있는 것인데 소스코드를 바이트코드로 변환해서 어디서든 호환이 될 수 있도록 하는 것이다. 바이트코드는 일종의 공용어라고 할 수 있다.
[한국어로 된 편지] → [영어로 번역] → [각 나라의 통역사가 자국어로 번역]
[자바 소스코드] → [바이트코드] → [각 운영체제의 JVM이 실행]
//[소스코드 직접 실행 시]
Windows → Windows 전용 실행파일 필요
Mac → Mac 전용 실행파일 필요
Linux → Linux 전용 실행파일 필요
//[자바의 바이트코드 방식]
→ Windows JVM
.class → Mac JVM 모두 동일한 결과
→ Linux JVM
이런 변환 과정은 수동이 아니라 JDK가 알아서 변환하는 것이다.
[개발 단계]
소스코드(.java) → JDK의 javac 컴파일러 → 바이트코드(.class)
[실행 단계]
바이트코드(.class) → JRE의 JVM → 프로그램 실행
자바가 하이브리드 방식인 이유와 최적화
//순수 컴파일 언어 (C/C++)
소스코드 → 컴파일 → 기계어(실행파일) → 실행
- 한번에 전체 변환
- 실행 속도 빠름
- OS마다 다시 컴파일 필요
//인터프리터 언어 (Python/JavaScript)
소스코드 → 한 줄씩 해석하며 실행
- 실시간 해석
- 실행 속도 비교적 느림
- 플랫폼 독립적
컴파일은 모든 과정을 한번 실행해서 변환하는 과정을 거치는 방식이다. 때문에 코드 전체에 명백한 에러가 존재하지 않아야만 실행이 가능하다.
인터프리터의 경우 코드를 한줄씩 실행하는 방식인데 실제로 1줄씩 실행한다기 보단 당장에 프로그램 상에서 온되는 부분까지만 실행한다에 가깝다. 예를 들어 프로그램 구동까지의 코드에 에러가 없다면 실행되는 것이다. 반면 구동 이후 추가적으로 실행하는 코드들, 예를 들어 버튼 클릭부분에 에러가 있다면 추가적으로 코드를 실행하려다가 에러가 발생하는 것이다.
//자바 (하이브리드)
[컴파일 단계]
소스코드 → 컴파일 → 바이트코드
[인터프리터 단계]
바이트코드 → JVM이 해석/실행
그런데 자바는 이처럼 컴파일 과정을 거친 다음 JVM으로 실행할 때는 인터프리터의 특성을 가지기때문에 하이브리드 방식이라고 하는 것이다.
다만 전통적인 인터프리터가 가지는 한계점을 어느정도 보완했는데
[전통적 인터프리터] : 소스코드 → 한줄씩 해석 → 실행
방식에서
[현대 자바 JVM]는 다음과 같은 방식을 사용해서 최적화를 진행한다.
- 처음: 바이트코드 → 인터프리터로 실행
- 자주 사용되는 코드 발견 → JIT 컴파일러가 기계어로 변환
- JIT(Just-In-Time): 실행 시점에 기계어로 변환
- 자주 쓰는 코드는 다시 해석할 필요 없음
- 성능 향상!
JIT 컴파일러의 실행 방식
//1. 초기 실행
소스코드(.java) → javac 컴파일러 → 바이트코드(.class)
↓
JVM
↓
인터프리터로 실행
//2. 반복 실행 감지
JVM이 "이 코드가 자주 실행되는군!"
↓
JIT 컴파일러 작동
↓
해당 부분을 기계어로 변환
↓
다음부터는 변환된 기계어를 직접 실행
// 이런 반복문이 있다면
for(int i=0; i<1000000; i++) {
someMethod(); // 백만 번 호출
}
// JIT 컴파일러가 someMethod를 기계어로 변환
// → 더 빠른 실행 가능
개발-실행 사이 컴파일 필요
일반적인 인터프리터 언어인 자바스크립트같은 경우 개발자 도구의 콘솔 창이나 HTML에서 소스코드를 직접 실행할 수 있다. 하지만 자바는 컴파일 언어의 특성을 가지기 때문에 직접 실행이 불가능하고 일단 컴파일 과정을 거쳐서 실행파일을 만들어야한다.
웹 기반 IDE나 온라인 컴파일러가 예외처럼 보일 수 있긴한데 실제로는 서버에서 컴파일 과정을 수행하고 사용자에게 결과만 보여주는 방식이다. 컴파일 과정이 숨겨져 있을 뿐 컴파일을 하지 않는 것은 아니다.
- 예시 서비스
- replit.com
- codesandbox.io
- programiz.com
[사용자 화면]
코드 작성 → 실행 버튼 클릭 → 결과 확인
[서버 내부에서]
1. 코드 받기
2. 컴파일 수행 (Java, C++ 등)
3. 실행
4. 결과 전송
겉으로는 직접 실행되는 것처럼 보이지만:
- 실제로는 서버에서 컴파일 과정 수행
- 컴파일의 필요성은 여전히 존재
- 단지 과정이 숨겨져 있을 뿐
런 타임 에러 VS 컴파일 타임 에러
흔히 프로그램을 실행하다 보면 런타임 에러가 발생하는 경우가 종종 있다. 그렇다면 런타임 에러란 무엇인가?
런타임 (Runtime)
- 프로그램이 실제로 실행되는 시점
- 실행 중에 발생하는 동적 오류를 검사
- 프로그램 실행 중에 발생
- 예시: null 포인터 참조, 배열 범위 초과 등
즉 런타임 에러란 실행 시점에서 발생하는 에러를 뜻하는 것이다. 기본적으로 우리가 사용하는 프로그램은 이미 컴파일 과정을 거친 것들이기 때문에 개발자가 아닌 일반 사용자가 별도로 컴파일 에러를 보는 경우는 없다고 할 수 있다.
컴파일 타임 (Compile Time)
- 소스 코드가 기계어로 변환되는 시점
- 주로 문법 오류, 타입 체크 등 정적 오류를 검사
- 프로그램 실행 전에 발생
- 예시: 변수 선언 오류, 문법 오류 등
간단한 비유:
- 컴파일 타임: 요리 재료 준비 단계에서의 검사 (재료가 상한 것은 아닌지, 필요한 양이 맞는지)
- 런타임: 실제 요리를 하면서 발생하는 문제 (요리 중 불이 너무 센 것, 조리 시간 초과 등)
그런데 자바는 컴파일 언어와 인터프리터 언어를 모두 사용하는 하이브리드 방식이라 개발 과정에서는 무조건 컴파일 에러를 볼 수 밖에 없다. 런타임 에러와는 별개로 말이다.
'Java > 자바 학습' 카테고리의 다른 글
2. 메모리 - 1. 스택(Stack)과 힙(Heap) (0) | 2025.05.12 |
---|---|
1. 자바 플랫폼 - 3. 바이트코드의 개념 (0) | 2025.05.12 |
1. 자바 플랫폼 - 1. JDK, JRE, JVM의 차이 (0) | 2025.05.12 |
ArrayList와 LinkedList의 주요 차이점 (0) | 2025.05.10 |
자바의 배열 (0) | 2025.05.09 |