Java의 탄생 배경
자바가 등장할 당시, C/C++과 같은 언어들이 주로 사용되었지만, 이 언어들은 특정 플랫폼(OS)이나 하드웨어에 맞춰 컴파일되도록 설계된 언어였다. 각 플랫폼마다 사용하는 CPU에 따라 해당 환경에 맞게 컴파일해야 했다.
이런 한계를 극복하기 위해 Sun Microsystems는 OS나 하드웨어에 상관없이 다양한 환경에서 실행될 수 있는 언어를 만들기로 결심했고, 이것이 자바의 탄생으로 이어졌다. 자바는 “Write Once, Run Anywhere”라는 철학을 기반으로 다양한 플랫폼에서 동일한 코드를 실행할 수 있도록 설계된 언어이다.
JAVA의 동작 원리
JVM을 설명하기 전에, 자바 프로그램이 어떻게 동작하는지 기본적인 원리를 먼저 살펴보겠다.
자바로 작성된 소스 파일은 보통 이름.java 형태로 저장된다. 이 자바 파일은 Java Compiler에 의해 .class 파일로 변환되고, 그 결과물은 바이트코드이다. 이 바이트코드는 JVM을 통해 실행된다.
그럼 JVM이 뭔데?
자바 프로그램의 동작에서 핵심적인 역할을 하는 것이 바로 JVM(Java Virtual Machine)이다. 이를 더 자세히 이해하기 위해, JVM의 구성 요소를 살펴보자.
Class Loader (클래스 로더)
클래스 로더는 자바의 클래스 파일(.class)을 JVM에 불러오는 역할을 한다. 이 과정은 크게 3단계로 이루어진다.
- Loading: 클래스 파일을 JVM에 적재하는 과정이다.
- Linking: 적재된 클래스를 검증하고, 필요한 데이터를 준비하는 과정이다.
- Initialization: 클래스 변수 등을 초기화하는 단계이다.
클래스 로더의 종류
먼저, 클래스 로더는 3가지 종류로 나뉜다.
- Bootstrap Class Loader: 가장 기본이 되는 클래스 로더이다. JDK의 핵심 클래스를 로드한다.
- Extension Class Loader: 부트스트랩 로더의 하위 클래스 로더로, 확장 라이브러리를 로드한다.
- Application Class Loader: 최종 클래스 로더로, 애플리케이션 클래스를 로드한다.
+) 99%는 Application Class Loader에서 찾을 수 있다.
1. Loading
클래스 로더는 .class 파일을 읽고 바이너리 코드들을 읽어서 Method area에 저장한다.
+) Method area는 밑에 Runtime Data Area에 나와있다!
+) Java 클래스는 한 번에 메모리에 올라가지 않고, 클래스 로더에 의해 애플리케이션에서 필요할 때만 메모리에 올라가게 된다.
2. Linking
링크 작업도 3단계로 이루어진다.
- Verify
- 혹시라도 바이트코드를 수정했을 수 있기 때문에 .class 파일 형식이 유효한지 체크한다.
- Preparation
- 클래스 변수(static 변수)와 기본 값에 필요한 메모리를 준비하는 과정
- Resolve
- 심볼릭 메모리 레퍼런스를 메서드 영역(Method Area)에 있는 실제 레퍼런스로 교체한다.
3. Initializing
Linking에서 Preparaion 단계에서 확보한 메모리 영역에 클래스의 static 값들을 할당한다.
부모 클래스와 자식클래스 초기화를 진행한다.
Runtime Data Area (런타임 데이터 영역)
JVM은 프로그램 실행 중 다양한 메모리 영역을 사용한다. 이를 Runtime Data Area라고 한다. 주요 메모리 영역은 다음과 같다.
- Method Area: 클래스에 대한 메타데이터를 저장하는 영역이다. 클래스 구조, 메서드, 필드에 대한 정보가 여기에 저장된다.
- Heap: 객체와 배열이 저장되는 공간이다. 이 영역은 주로 가비지 컬렉터가 관리한다.
- Stack: 각 스레드가 사용하는 메모리 공간이다. 메서드 호출 시 스택 프레임이 생성되며, 로컬 변수와 임시 데이터를 저장한다.
- PC(Program Counter) Register: 각 스레드가 현재 실행 중인 명령어의 주소를 저장하는 작은 메모리 영역이다.
- Native Method Stack: 자바 외의 네이티브 코드를 실행할 때 사용되는 스택이다.
Execution Engine (실행 엔진)
Execution Engine은 JVM에서 바이트코드를 실제로 실행하는 부분이다. 이 과정에서 두 가지 방식으로 실행이 이루어진다.
- Interpreter (인터프리터): 바이트코드를 한 줄씩 해석해 실행하는 방식이다. 프로그램을 빠르게 시작할 수 있지만, 성능이 떨어질 수 있다.
- JIT(Just-In-Time) Compiler (JIT 컴파일러): 자주 실행되는 코드를 기계어로 변환해 성능을 높이는 방식이다. JIT 컴파일러는 핫스팟(HotSpot)이라는 자주 사용되는 부분을 발견해 그 부분을 최적화한다.
- C1 Compiler: 빠르게 기계어로 변환하여 프로그램 시작 속도를 높인다.
- C2 Compiler: 더 정교하게 최적화된 기계어로 변환하여 장기적인 성능을 향상시킨다.
자바가 C 언어보다 느리다는 평가를 받는 이유는, 자바의 바이트코드가 실행 중에 인터프리터로 해석되어야 하기 때문이다. 인터프리터 방식은 프로그램을 한 줄씩 해석하기 때문에, 런타임 전에 미리 기계어로 변환하는 C/C++ 같은 컴파일 방식보다는 느릴 수밖에 없다. 하지만 JIT 컴파일러 덕분에 성능이 크게 개선되었고, 현재는 초기보다는 훨씬 빨라졌다.
Garbage Collector(GC)
가비지 컬렉터에 대한 내용은 제 블로그를 참고하면 좋을 거 같다.
'JAVA' 카테고리의 다른 글
큰수 만들기 (1) | 2024.10.02 |
---|---|
읽기 쉬운 코드 만들기 with 세션 (2 / 2) (3) | 2024.10.01 |
읽기 쉬운 코드 만들기 with 세션 (1 / 2) (0) | 2024.09.30 |
캡슐화에 대한 정리 with 세션 (0) | 2024.09.26 |
UncheckedException과 CheckedException (1) | 2024.09.13 |
ArrayList는 어떻게 크기가 조절될까? (0) | 2024.09.11 |
Garbage Collection(GC) 더 자세히 살펴보기 (0) | 2024.09.09 |
Java final과 불변성 (0) | 2024.09.08 |