자바를 공부하다 보면 Write once, run anywhere (한 번 작성하면, 어디서나 실행된다.) 라는 말을 볼 수 있다.
컴퓨터가 어떤 프로그램을 실행하려면, 컴퓨터가 읽을 수 있는 언어(기계어)로 작성되어야 한다.
자바 프로그램에서는 JVM(Java Virtual Machine)이라는 가상머신이 우리가 작성한 java코드를 기계어로 변환해 주고, 어떤 운영체제에서도 이 코드가 실행될 수 있도록 도와주는 역할을 한다.
즉, JVM 덕분에 OS에 독립적인 특징을 가지고 있는 것이다.
JVM 작동 원리
우리가 작성한 코드가 JVM에 전달되어 실행되기까지 과정은 아래와 같다.
- 개발자가 자바 소스코드(.java)를 작성한다.
- 자바 컴파일러(javac)가 자바 소스코드를 자바 바이트코드(.class)로 컴파일한다.
바이트코드(.class)는 JVM이 이해할 수 있는 코드로 명령어의 크기가 1바이트라서 자바 바이트 코드라고 불린다.
아직 컴퓨터가 이해할 수 없는, JVM만 읽을 수 있는 코드다. - JVM은 자바 바이트코드(.class)를 읽고, 운영체제에서 실행 가능한 기계어로 변환하여 실행한다.
JVM이 3번의 과정을 어떻게 수행하는지 알아보자.
JVM 구조
JVM의 구조는 크게 4가지로 이루어져 있다.
- GC(Garbage Collector, 가비지 컬렉터)
- Class Loader(클래스 로더)
- Execution Engine(실행엔진)
- Runtime Data Area(런타임 데이터 영역)
1. GC(Garbage Collector, 가비지 컬렉터)
JVM은 가비지 컬렉터(이하 GC)를 이용해 더는 사용하지 않는 메모리를 자동으로 회수한다.
개발자가 따로 메모리를 관리하지 않게 도와주는 것이다.
GC는 힙 영역에 생성된 객체 중 참조되지 않는 객체들을 자동으로 메모리에서 제거한다.
(그림에서 스펠링 오류가 있는데 Heap이다.)
2. Class Loader(클래스 로더)
자바는 기본적으로 동적 로딩(Dynamic Loading) 방식을 사용한다.
동적 로딩이란 프로그램이 실행될 때 필요한 클래스를 메모리에 즉시 불러오는 것을 의미한다.
즉, 프로그램이 실행될 때 모든 클래스를 한 번에 로드하는 게 아니라 실제로 필요한 시점(해당 클래스가 참조되었을 때) 해당 클래스를 찾아 메모리에 올린다.
이 동적 로딩을 담당하는 것이 클래스 로더이다.
클래스로더는 자바 프로그램의 바이트코드를 읽고, 필요한 클래스를 메모리로 로드하는 역할을 한다.
3. Execution Engine(실행엔진)
앞서 말했던 클래스 로더에 의해 JVM으로 로드된 바이트코드(.class)들을 기계어로 변환하여 실행하는 역할을 한다.
실행 엔진은 두 가지 방식을 함께 사용한다.
- 인터프리터
바이트 코드 명령어를 한 줄씩 읽고 해석하여 실행한다.
빠르게 실행할 수 있지만, 같은 코드를 실행할 때마다 바이트코드를 매번 해석해야 해 속도가 느리다. - JIT(Just-In-Time Compiler)
인터프리터의 단점을 보완하기 위해, 반복적으로 호출되는 메서드를 기계어로 미리 컴파일하여 실행 속도를 높인 방식이다.
JIT가 컴파일한 코드는 캐시에 저장되어 재사용할 수 있다.
실행 엔진은 프로그램 실행 초기에는 인터프리터 방식으로 빠르게 시작하고 실행 중에 JIT 컴파일러가 분석을 통해 성능이 중요한 부분을 식별해 네이티브 코드로 컴파일한다.
4. Runtime Data Area(런타임 데이터 영역)
런타임 데이터 영역은 자바 코드의 메모리 영역(스택&힙)으로 자바 애플리케이션을 실행할 때 사용되는 데이터들을 메모리에 적재하는 곳이다. 해당 데이터들을 용도에 따라 여러 영역으로 나누어 관리한다.
Method와 Heap 영역은 모든 스레드에서 공유되고, 나머지 스택(Stack), PC 레지스터, 네이티브 메서드 스택 영역은 스레드마다 각각 존재한다.
메서드 영역(Method Area)
모든 스레드가 공유하는 영역으로, 클래스 메타데이터(클래스 이름, 부모 클래스 이름, 변수 정보, 접근 제어자 정보 등), 상수 풀(Constant Pool), 메서드 코드가 저장되는 영역이다.
힙(Heap)
모든 스레드가 공유하는 영역으로, 생성된 모든 객체와 배열이 저장되는 영역이다.
가비지 컬렉터(GC)가 더 이상 참조하지 않는 객체를 제거하는 공간이기도 하다.
스택(Stack)
스레드마다 독립적으로 할당되는 영역으로, 메서드 호출 시 생성되는 지역 변수와 메서드 호출 상태가 저장되는 영역이다.
스택 프레임(Stack Frame) 단위로 메모리를 관리하며, 메서드 호출 시 스택 프레임이 생성되고 메서드가 종료되면 해당 프레임이 제거된다.
이 스택이 가득 찼을 때 발생하는 오류가 StackOverFlowError이다.
PC 레지스터(Program Counter Register) 영역
스레드마다 독립적으로 할당되는 영역으로, 현재 실행 중인 명령어의 주소가 저장된다.
자바 바이트코드는 스택 기반의 가상 머신으로, PC Register는 스레드가 실행할 다음 명령어의 주소를 저장하여 명령어가 순차적으로 실행될 수 있도록 돕는다.
네이티브 메서드 스택(Native Method Stack) 영역
자바 프로그램이 네이티브 메서드(C/C++와 같은 자바 외부 언어로 작성된 코드)를 실행할 때 사용되는 영역이다.
JVM 구동 방식
위 JVM 구조를 바탕으로 구동 방식을 정리해 보자.
- 개발자가 자바 소스코드(.java)를 작성한다.
- 자바 컴파일러가 자바 소스코드(.java)를 읽어 바이트코드(.class)로 컴파일한다. (.java -> .class)
- JVM이 실행되고 클래스 로더는 컴파일된 바이트코드(.class)를 JVM의 메모리에 로드한다.
- JVM의 실행 엔진이 메모리에 존재하는 정보를 가지고 바이트코드(.class)파일을 실행한다.
- 프로그램 실행이 완료되면 JVM이 종료되고, 할당된 모든 메모리가 해제된다.
참고
JVM이란? 구조와 특징에 대해 알아보자.
자바 컴파일 과정
[Java] 자바 컴파일 과정 & JVM 내부 구조
'💻 Dev > Java' 카테고리의 다른 글
BigDecimal이란? (0) | 2024.10.30 |
---|---|
Immutable(불변성), StringBuffer와 StringBuilder (0) | 2024.10.29 |
자바 코드의 메모리 영역(스택&힙) (0) | 2024.10.29 |
예외(Checked Exception, Unchecked Exception) (0) | 2024.10.26 |
interface와 abstract(추상) 클래스 (0) | 2024.10.25 |
동등성과 동일성&String.equals() (0) | 2023.11.05 |