자바의 실행 환경
1. JVM이란?
JVM(Java Virtual Machine)은 이름 그대로 '자바를 실행하기 위한 가상 기계'입니다. Java의 가장 큰 특징 중 하나는 OS에 종속적이지 않다는 점인데, 이것이 가능한 이유가 바로 이 JVM 때문입니다.
간단히 말해 JVM은 운영체제에 종속받지 않고 CPU가 Java 코드를 인식하고 실행할 수 있게 하는 가상 컴퓨터입니다. 다양한 플랫폼에서 동일한 Java 애플리케이션을 실행할 수 있게 해주는 핵심 요소입니다.
Java 실행 과정의 이해
Java 소스코드(.java)는 CPU가 직접 인식할 수 없기 때문에 기계어로 컴파일해야 합니다. 하지만 Java의 경우 일반적인 컴파일 언어와는 과정이 조금 다릅니다.
- Java 소스코드(.java)는 먼저 Java 컴파일러(javac)에 의해 바이트코드(.class)로 변환됩니다.
- 이 바이트코드는 아직 기계어가 아니므로 OS가 직접 실행할 수 없습니다.
- 대신 JVM이 이 바이트코드를 읽고 해석하여 실행합니다.
이런 방식 덕분에 "Write Once, Run Anywhere" (한 번 작성하고 어디서나 실행)이라는 Java의 철학이 가능해집니다. 각 OS별로 JVM만 있다면, 동일한 바이트코드를 모든 플랫폼에서 실행할 수 있기 때문입니다.
2. JDK, JRE, JVM의 관계
Java 개발과 실행 환경을 이해하려면 세 가지 핵심 개념을 알아야 합니다:
☑️JDK (Java Development Kit)
- Java 개발에 필요한 모든 도구를 포함한 종합 패키지
- 컴파일러(javac), 디버거(jdb) 등 개발 도구 포함
- JRE를 포함하고 있음
- Java 프로그램을 작성, 컴파일, 실행할 수 있는 환경 제공
☑️JRE (Java Runtime Environment)
- Java 프로그램을 실행하는 데 필요한 환경
- JVM + Java 클래스 라이브러리로 구성
- 컴파일된 Java 프로그램을 실행하는 데 필요한 패키지
☑️JVM (Java Virtual Machine)
- 바이트코드를 실행하는 가상 머신
- 플랫폼 독립성 제공
- 메모리 관리, 가비지 컬렉션 등 담당
이를 도식화하면:
JDK > JRE > JVM
즉, JDK는 JRE를 포함하고, JRE는 JVM을 포함하는 구조입니다.
3. 바이트코드란?
바이트코드(Bytecode)는 가상 컴퓨터에서 실행되는 프로그램을 위한 이진 표현법입니다. Java에서는 JVM이 이해할 수 있는 언어로 변환된 Java 소스코드를 의미합니다.
이름이 '바이트코드'인 이유는 명령어 크기가 1바이트라서 그렇게 불리게 되었습니다.
바이트코드와 바이너리코드의 차이
- 바이트코드: JVM이 이해할 수 있는 중간 언어
- 바이너리코드: OS와 CPU가 직접 실행할 수 있는 기계어 (0과 1로 구성)
바이트코드는 JVM 내부에서 인터프리터나 JIT 컴파일러에 의해 최종적으로 바이너리코드(기계어)로 변환되어 실행됩니다.
실제 컴파일 과정 확인하기
터미널에서 다음과 같이 실행하면 Java 소스 파일이 바이트코드로 컴파일됩니다:
$ javac Solution.java # .java 파일을 .class 파일(바이트코드)로 변환
$ java Solution # 컴파일된 바이트코드를 JVM에서 실행
4. JIT 컴파일과 인터프리터
☑️JIT 컴파일(Just-In-Time Compilation)이란?
JIT 컴파일은 프로그램을 실제 실행하는 시점에 바이트코드를 기계어로 번역하는 기술입니다. 인터프리터 방식의 단점을 보완하기 위해 도입되었습니다.
✔️인터프리터 vs 컴파일러
항목 | 🟦 인터프리터 (Interpreter) | 🟥 컴파일러 (Compiler) |
실행 방식 | 코드를 한 줄씩 즉시 해석 후 실행 | 전체 코드를 한 번에 번역 후 실행 |
실행 속도 | 느림 (실행할 때마다 해석) | 빠름 (기계어로 미리 번역) |
에러 처리 | 에러가 발생하면 그 줄에서 멈춤 | 전체 컴파일 도중 모든 에러를 체크 |
대표 예시 | Python, JavaScript, JVM의 Interpreter | C, C++, JVM의 JIT Compiler |
생성 결과 | 별도 기계어 파일 없음 | 기계어 파일(.exe, .class) 생성 |
실행 전 요구사항 | 소스 코드만 있으면 실행 가능 | 컴파일 단계 필수 |
유연성 | 동적 (실행 중 코드 수정 가능) | 정적 (수정 후 다시 컴파일 필요) |
✔️컴파일 vs 컴파일러
컴파일은 번역 행위이고, 컴파일러는 그 번역을 수행하는 도구이다.
항목 | 컴파일 (Compile) | 컴파일러 (Compiler) |
의미 | 소스 코드를 기계어 또는 중간 코드로 번역하는 행위 | 그 번역 작업을 수행하는 프로그램 또는 시스템 |
형태 | 동작(행위), 동사적인 개념 | 도구, 명사적인 개념 |
예시 | "자바 파일을 컴파일한다" | javac, gcc 등 |
관계 | 컴파일러가 컴파일을 수행함 | 컴파일을 수행하는 주체 |
☑️JIT 컴파일러의 동작 방식
JIT 컴파일러는 다음과 같이 동작합니다:
- 처음에는 인터프리터 방식으로 바이트코드를 한 줄씩 실행
- 자주 실행되는 코드(반복되는 호출 및 루프(핫스팟))를 감지
- 해당 코드를 기계어로 컴파일하여 캐시에 저장
- 이후 해당 코드가 실행될 때는 인터프리팅 대신 컴파일된 기계어를 직접 실행
이런 방식을 비유하자면:
- 바이트코드: "요리 레시피 카드"
- 인터프리터: 매번 레시피를 읽고 직접 손으로 요리함 (기계어 생성 안 함)
- JIT: 자주 쓰는 레시피는 미리 준비해두고 자동 기계로 요리 (기계어로 변환)
5. JVM의 구성요소
JVM은 크게 다음과 같은 구성요소로 이루어져 있습니다:
Java Compiler → Class Loader (Loading → Linking → Initialization) → Runtime Data Area → Execution (Interpreter/JIT) → 종료
1. Java Source Code 작성 (.java)
2. Java Compiler (javac)로 컴파일 → .class (바이트코드) 생성
3. JVM 시작
4. Class Loader 작동
Loading: .class 파일을 메모리로 로드
Linking
Verification: 바이트코드가 유효한지 확인
Preparation: static 변수 공간 할당
Resolution: 심볼릭 레퍼런스를 실제 메모리 주소로 변환
Initialization: static 초기화 블록과 static 변수 실행
5. Runtime Data Area에 로드된 클래스 데이터 저장
6. Execution 단계
Interpreter: 바이트코드를 한 줄씩 해석해서 실행
JIT Compiler: 자주 실행되는 바이트코드를 기계어로 변환해 성능 향상
Garbage Collector: 사용하지 않는 객체 정리
7. 필요 시 Native Method Interface 통해 C/C++ 등 네이티브 라이브러리 호출
☑️클래스 로더(Class Loader)
클래스 파일(*.class)을 JVM 내로 로드하고, 링크( JVM이 실행할 수 있도록 연결/준비하는 과정 )를 통해 배치하는 모듈입니다. 런타임에 동적으로 클래스를 로드하고 JAR 파일 내 저장된 클래스들을 JVM 위에 탑재합니다.
☑️실행 엔진(Execution Engine)
클래스 로더가 JVM 내의 런타임 데이터 영역에 배치시킨 바이트코드를 실행합니다. 실행 엔진은 다음 두 가지 방식으로 바이트코드를 실행합니다:
✔️인터프리터(Interpreter)
- 바이트코드를 한 줄씩 읽어서 실행
- 해석이 빠르지만 실행은 느림
✔️JIT 컴파일러(Just-In-Time Compiler)
- 자주 사용되는 코드를 발견하면 전체 바이트코드를 기계어로 컴파일
- 컴파일된 코드는 캐시에 보관되어 빠르게 실행
✔️가비지 콜렉터(Garbage Collector)
더 이상 사용되지 않는 객체를 찾아 메모리에서 삭제하는 역할을 담당합니다.
☑️런타임 데이터 영역(Runtime Data Area)
JVM이 프로그램을 실행하기 위해 OS로부터 할당받은 메모리 공간입니다. 다음과 같은 영역으로 구성됩니다:
✔️PC 레지스터(PC Register)
- 스레드가 시작될 때 생성되며, 스레드마다 하나씩 존재
- 현재 실행 중인 명령의 주소를 저장
✔️JVM 스택 영역(JVM Stack)
- 메소드 호출 시 생성되는 프레임을 저장
- 지역 변수, 매개변수, 리턴 값 등 임시 데이터 저장
- 메소드 종료 시 함께 소멸
✔️네이티브 메소드 스택(Native Method Stack)
- Java가 아닌 다른 언어(C/C++ 등)로 작성된 코드를 위한 스택
- JNI(Java Native Interface)를 통해 호출되는 네이티브 코드를 위한 공간
✔️메소드 영역(Method Area)
- 클래스 정보를 저장하는 공간
- 클래스 로더에 의해 로드된 클래스 및 인터페이스 정보 보관
- static 변수, 상수 등이 저장됨
✔️✔️ 런타임 상수 풀(Runtime Constant Pool)
- 메소드 영역 내에 존재하는 별도의 관리 영역
- 상수 자료형을 저장하고 참조하며 중복을 방지
- 리터럴 문자열, 상수 필드 값 등이 저장됨
public class Example {
String s = "abc"; // ✅ 리터럴 → Runtime Constant Pool
final static int LIMIT = 50; // ✅ 상수 → Runtime Constant Pool
double pi = 3.14; // ✅ 숫자 리터럴 → Runtime Constant Pool
void method() {
System.out.println("hi"); // ✅ "hi" → Runtime Constant Pool
}
}
✔️힙 영역(Heap)
- 객체와 배열이 생성되는 공간
- 가비지 컬렉션의 대상이 되는 영역
- 다음과 같은 세부 영역으로 나뉨:
✔️✔️Young 영역(Young Generation)
- 새로 생성된 객체가 저장되는 공간
- 대부분의 객체는 금방 접근 불가능 상태가 되기 때문에 이 영역에서 생성되었다가 사라짐
- Minor GC가 발생하는 영역
✔️✔️✔️Eden
- 객체들이 최초로 생성되는 공간
✔️✔️✔️
Survivor 0, 1
- Eden에서 참조되는 객체들이 저장되는 공간
- Eden 영역이 가득 차면 Minor GC가 발생하고, 살아남은 객체들이 old로 이동
✔️✔️Old 영역(Old Generation)
- Young 영역에서 일정 시간 이상 살아남은 객체들이 이동하는 공간
- Major GC가 발생하는 영역으로, Minor GC보다 속도가 느림
✔️✔️Permanent Generation
- 클래스와 메소드의 메타데이터 등이 저장되는 영역
- Java 8부터는 Metaspace로 대체됨(메모리 제한 없음)
☑️변수 저장 위치 요약
변수 종류 | 저장 위치 | 설명 |
인스턴스 변수 | ✅ Heap | 객체와 함께 힙에 저장됨 |
지역 변수 | ✅ Stack | 메소드 호출 시 JVM Stack에 저장됨 |
static 변수 | ✅ Method Area | 클래스당 1개만 존재, 객체와 무관 |
☑️가비지 컬렉션(GC) 대상
메모리 영역 | GC 대상 | 설명 |
Heap | ✅ 예 | new로 생성된 객체 |
Stack | ❌ 아니오 | 메소드 호출 시 자동 생성/제거 |
Method Area | ❌ 보통 아니오 | 클래스, static 등 저장 |
Native Method Stack | ❌ 아니오 | JNI용, OS가 관리 |
"더 이상 사용되지 않는 객체 = 가비지(Garbage)" = GC의 수거 대상
GC 입장에서 "안 쓰는 객체"란?
- 현재 어떤 변수도 참조하지 않는 객체
- 즉, 다시는 접근할 수 없는 객체
- 프로그램 실행 흐름상 더 이상 도달 불가능한 객체
비유로 정리하면
- "쓰레기"는 방 안에 있지만 아무도 다시 안 쓸 물건
- GC는 이걸 찾아서 치워주는 청소부
6. JAR과 WAR 파일의 차이
☑️JAR (Java ARchive)
- Java 애플리케이션을 배포하기 위한 패키지 파일 형식
- 클래스 파일, 리소스, 메타데이터를 포함
- Java 라이브러리, 독립 실행형 애플리케이션 배포에 사용
- 명령줄에서 직접 실행 가능 (executable JAR의 경우)
☑️WAR (Web Application aRchive)
- 웹 애플리케이션을 배포하기 위한 JAR 파일의 확장 형식
- JSP, 서블릿, 자바 클래스, HTML, JavaScript 등의 웹 애플리케이션 자원 포함
- 웹 서버나 애플리케이션 서버(Tomcat, JBoss 등)에 배포되어 실행
- 특정 디렉토리 구조(WEB-INF 등)를 따름
☑️주요 차이점
특성 | JAR | WAR |
용도 | 일반 Java 애플리케이션/라이브러리 | 웹 애플리케이션 |
구조 | 자유로움 | WEB-INF 등 정해진 구조 필요 |
실행 환경 | JRE만 있으면 실행 가능 | 웹 서버/애플리케이션 서버 필요 |
직접 실행 | 가능(executable JAR) | 불가능 |
확장자 | .jar | .war |
[JAVA] JVM이란? 개념 및 구조 (JDK, JRE, JIT, 가비지 콜렉터...)
JVM이란 무엇인가 Java Virtual Machine의 줄임말. 직역하면 '자바를 실행하기 위한 가상 기계(컴퓨터)'라고 할 수 있다. Java 는 OS에 종속적이지 않다는 특징을 가지고 있다. OS에 종속받지 않고 실행되
doozi0316.tistory.com
이분꺼 참고해서 3시간 넘게 작성했다...
이상으로 JVM의 구조와 동작 원리에 대해 알아보았습니다. Java의 플랫폼 독립성을 가능하게 하는 JVM의 역할과 내부 구성요소들을 이해하면, Java 프로그래밍을 더 효율적으로 할 수 있을 것이다!