[CS] 자바 실행 과정 및 JVM
자바는 한 번 작성하면 어디서든 실행할 수 있는 "Write Once, Run Anywhere"라는 철학을 바탕으로, 플랫폼 독립성을 가진 대표적인 프로그래밍 언어다. 이러한 플랫폼 독립성은 JVM(Java Virtual Machine)이라는 가상 머신 덕분에 가능한데, 관련해서 무엇인지 작성해보겠다.
1. 자바 실행 과정의 특징
자바 프로그램의 실행 과정은 크게 다음과 같다.
소스 코드 작성 및 컴파일
개발자는 .java 확장자를 갖는 자바 소스 코드를 작성하면, javac 컴파일러가 이 소스 코드를 바이트코드(.class)로 컴파일한다.
- 바이트코드: 특정 운영체제에 종속되지 않는 중간 형태의 코드
클래스 로딩 및 링크(Linking)
실행 시점에 JVM은 클래스 로더(Class Loader)를 통해 필요한 .class 파일(바이트코드 파일)을 메모리에 로드하고, 심볼릭 참조를 직접 참조로 변경하는 링크 과정을 수행한다.
바이트코드 실행
로드된 바이트코드는 실행 엔진에 의해 해석되거나 JIT(Just-In-Time) 컴파일러를 통해 기계어로 변환되어 실제 OS 환경에서 실행된다.
이 과정을 통해 자바는 특정 운영체제나 하드웨어 아키텍처에 종속되지 않고 동일한 바이트코드를 기반으로 다양한 환경에서 실행될 수 있는 것이다.
2. JVM의 구조 및 역할
JVM은 자바 프로그램이 실질적으로 동작하는 "가상 머신" 환경으로, 운영체제 위에서 자바 바이트코드를 해석하고 실행하는 역할을 한다.
클래스 로더(Class Loader)
클래스 로더는 자바 클래스(.class 파일)를 동적으로 메모리에 로드하고, 필요한 경우 클래스를 초기화하는 역할을 담당한다. 자바 프로그램 실행 중 필요한 클래스를 언제든 불러올 수 있으며, 이를 통해 유연한 모듈성 및 동적 로딩이 가능하다.
- 로딩: .class 파일을 찾아서 JVM 메모리에 로드
- 링크: 심볼릭 레퍼런스(클래스, 메소드, 필드 정보 등)를 다이렉트 레퍼런스로 변경하고, 정적 변수를 초기화
- 초기화: 정적 블록 실행 및 정적 변수의 초기값 할당 등 클래스 초기화 과정 수행
실행 엔진(Execution Engine)
실행 엔진은 로딩된 바이트코드를 실제 CPU가 이해할 수 있는 기계어 수준으로 해석하고 실행한다.
- 인터프리터: 바이트코드를 명령어 단위로 해석하며 순차적으로 실행. 초기 실행 속도가 빠르지만, 반복 실행되는 코드에서는 비효율적.
- JIT 컴파일러: 자주 실행되는 바이트코드 영역을 선별하여 기계어로 컴파일함으로써, 반복되는 코드 실행 시 성능을 향상시킴. 실행 시간이 지날수록 최적화가 이루어져 퍼포먼스가 향상.
메모리 영역(Runtime Data Area)
JVM은 자바 프로그램 실행 중 다양한 데이터를 저장한다. 메모리 영역은 다음과 같다.
- 메소드 영역: 클래스에 대한 메타데이터(클래스 정보, 메소드 정보, 상수 풀(Constant Pool), 정적 변수) 등을 저장.
- 힙: new 키워드를 통해 생성되는 객체와 배열이 저장. 가비지 컬렉터에 의해 사용되지 않는 객체 정리.
- JVM 스택: 메소드 호출 시 스택 프레임을 생성하여 지역 변수, 파라미터, 연산 중간 결과 저장. 메소드가 종료되면 해당 스택 프레임은 제거.
- PC(Program Counter) 레지스터: 현재 실행 중인 JVM 명령어의 주소를 저장.
- 네이티브 메소드 스택: 자바 외부의 네이티브 코드(C, C++) 호출 시 사용되는 스택 영역.
자바의 플랫폼 독립성
JVM은 자바 바이트코드를 어떤 플랫폼(Windows, Mac OS, Linux) 위에서도 동일하게 해석하고 실행한다(플랫폼 독립성). 즉, 자바 개발자는 OS나 하드웨어 환경에 구애받지 않고 하나의 바이트코드만으로 다양한 환경에서 애플리케이션을 동작할 수 있는 것이다(이식성).