실행 과정
- 소스 코드 작성: 이 부분은 JVM과는 직접적인 연관이 없습니다. 개발자가 자바 소스 코드를 작성하는 단계입니다.
JVM과는 관련이 있지만 JVM 내부 동작은 아닙니다.
- 컴파일: 자바 컴파일러가 소스 코드를 바이트 코드로 컴파일하는 과정입니다.
- 바이트 코드 생성: 컴파일러가 바이트 코드를 생성하는 단계입니다.
여기서부터 JVM이 관여합니다.
-
클래스 로딩: 클래스 로더가 컴파일된 바이트 코드를 메모리에 로드하고 링크합니다. JVM의 클래스 로더에게 해당됩니다.
-
실행 엔진
- 인터프리터
- 바이트 코드를 한 줄씩 가져와서 해석하고 실행합니다.
- 실행 중에 바이트 코드를 해석하여 해당 기계어를 실행하는 방식입니다.
- 빠르게 실행될 수 있지만, 반복적으로 해석과 실행을 진행하므로 전체적인 실행 속도가 느릴 수 있습니다.
- 코드를 실행하는 즉시 바로 인터프리팅하므로 초기 실행이 빠르고, 메모리 사용량이 작을 수 있습니다.
- JIT 컴파일러 (Just-In-Time Compiler)
- 바이트 코드를 실행하기 전에 바이트 코드 전체를 컴파일하여 바이너리 코드로 변경합니다.
- 컴파일된 코드는 이후 실행될 때 다시 인터프리팅하는 것이 아니라 직접 실행됩니다.
- 초기 실행 속도는 인터프리터보다 느릴 수 있지만, 반복 실행되는 코드에 대해서는 빠른 성능을 제공합니다.
- 코드를 컴파일하는데 시간이 필요하고, 메모리를 더 사용할 수 있지만, 반복 실행되는 코드에 대한 성능 향상을 제공합니다.
따라서 인터프리터는 빠르게 코드를 실행할 수 있지만 전체적인 실행 속도가 느릴 수 있고, JIT 컴파일러는 초기 실행 속도가 느릴 수 있지만 반복 실행되는 코드에 대해서는 빠른 성능을 제공합니다. Java의 JVM은 이러한 두 가지 방식을 조합하여 최적의 성능을 추구합니다.
이러한 과정에서 컴파일러는 바이트 코드를 생성하고, JVM은 이를 로드하고 실행하는 역할을 담당합니다. JVM은 실행 엔진을 통해 바이트 코드를 실행 가능한 형태로 처리하여 프로그램이 작동하도록 합니다.
추가 내용
JVM이란
JVM(Java Virtual Machine)은 자바 프로그램이 실행되는 가상 컴퓨터입니다. 이것은 자바 프로그램이 특정 운영 체제나 하드웨어에 종속되지 않도록 하는 핵심적인 부분입니다.
주요 기능
- 바이트 코드 실행: JVM은 자바 컴파일러에 의해 생성된 바이트 코드(.class 파일)를 읽고 실행합니다. 이러한 바이트 코드는 특정 기계어가 아닌 JVM이 이해할 수 있는 명령어로, 플랫폼에 구애받지 않고 실행될 수 있습니다.
- 메모리 관리: JVM은 메모리 할당 및 해제를 관리합니다. 이는 객체의 생성과 소멸, 메모리 누수 방지 등을 포함합니다.
- 가비지 컬렉션: JVM은 가비지 컬렉션을 통해 더 이상 사용되지 않는 객체들을 자동으로 정리하여 메모리를 관리합니다.
- 예외 처리: JVM은 자바 프로그램에서 발생하는 예외를 처리하고 관리합니다.
JVM은 자바 어플리케이션의 포팅(porting)과 이식성을 높여주며, 프로그램이 여러 운영 체제나 환경에서 동일하게 실행될 수 있도록 합니다. 다양한 운영 체제에서 동일한 자바 프로그램을 실행할 수 있게 해주는 것이 JVM의 큰 장점 중 하나입니다.
바이트코드 변환
바이트 코드 변환은 자바 컴파일러(javac)에 의해 수행됩니다. 자바 프로그램은 소스 코드로 작성되며, 이 소스 코드는 컴파일러를 통해 바이트 코드로 변환됩니다.
- 소스 코드 작성: 먼저, 자바 프로그램을 텍스트 파일에 작성합니다. 이 파일은 .java 확장자를 가지고 있습니다.
- 컴파일: 소스 코드를 자바 컴파일러(javac)로 전달합니다. 컴파일러는 소스 코드를 읽고 문법적으로 올바른지 확인하며, 문제가 없다면 해당 코드를 바이트 코드로 번역합니다.
- 바이트 코드 생성: 컴파일러가 소스 코드를 컴파일하면, 그 결과로 .class 확장자를 가진 바이트 코드 파일이 생성됩니다. 이 파일에는 JVM이 실행할 수 있는 중간 형태의 명령어들이 포함되어 있습니다.
- 바이트 코드 실행: 생성된 바이트 코드 파일은 JVM에 의해 실행됩니다. JVM은 해당 바이트 코드를 해석하거나 JIT(Just-In-Time) 컴파일러를 통해 네이티브 코드로 변환하여 실행합니다.
이러한 과정을 통해 자바 소스 코드가 기계어가 아닌 바이트 코드로 변환되어 JVM 위에서 실행됩니다. 이는 자바의 플랫폼 독립성을 제공하며, 한 번 작성한 코드를 여러 플랫폼에서 실행할 수 있게 해줍니다.
클래스 로더와 실행 엔진
-
클래스 로더(Class Loader)
- 로딩(Loading): 클래스 로더는 자바 프로그램이 실행될 때, 필요한 클래스들을 로딩합니다. 필요한 클래스는 .class 파일 형태로 저장되어 있으며, 클래스 로더는 이를 메모리로 로드합니다.
- 링크(Linking): 로딩된 클래스들은 링크 과정을 거칩니다. 이 과정에서는 바이트 코드의 검증(Verification), 준비(Preparation), 해결(Resolution)이 이뤄집니다. 검증은 바이트 코드가 유효한지 확인하고, 준비는 클래스 변수들을 메모리에 할당하고 기본 값으로 초기화합니다. 해결은 심볼릭 레퍼런스를 실제 메모리 참조로 변환하는 과정을 의미합니다.
- 초기화(Initialization): 클래스가 처음 사용될 때, 해당 클래스의 정적 변수들을 초기화하고 정적 초기화 블록(static initializer block)이 실행됩니다.
클래스 로더는 세 가지 주요 부분으로 구성되어 있으며, 로드된 클래스들은 메소드 영역(Method Area)에 저장됩니다.
-
실행 엔진(Execution Engine)
- 인터프리터(Interpreter): 실행 엔진은 바이트 코드를 읽고 실행합니다. 이때, 인터프리터를 사용하여 바이트 코드를 한 줄씩 읽어 해당 플랫폼의 기계어로 번역하고 실행합니다. 이는 빠른 실행 속도보다는 유연성과 이식성을 제공합니다.
- JIT 컴파일러(Just-In-Time Compiler): 일부 JVM 구현체들은 인터프리터와 함께 JIT 컴파일러를 사용합니다. JIT는 반복적으로 실행되는 코드를 실시간으로 기계어로 번역하여 캐시에 저장합니다. 이후 동일한 코드가 다시 실행될 때 캐시된 기계어 코드를 사용하여 성능을 향상시킵니다.
실행 엔진은 JVM의 핵심 부분으로, 클래스 로더에 의해 로드된 클래스들을 실행하고 결과를 내는 역할을 수행합니다. 이는 JVM이 자바 바이트 코드를 실행 가능한 코드로 변환하고 실행하는 주체입니다.
런타임 데이터 영역
자바 가상 머신(JVM)의 런타임 데이터 영역(Runtime Data Area)은 JVM이 프로그램을 실행하는 동안 데이터를 보관하고 관리하는 메모리 영역입니다. 주요한 데이터 영역으로는 다음과 같은 부분들이 있습니다.
- 메소드 영역(Method Area)
- 클래스 정보와 정적 변수들을 저장하는 공유 메모리 영역입니다.
- 로딩된 클래스의 바이트 코드, 메소드 및 클래스 변수, 상수, 메소드 코드 등의 정보를 보관합니다.
- JVM이 시작될 때 생성되고, 모든 스레드에서 공유합니다.
- 힙(Heap)
- 동적으로 생성된 객체들을 저장하는 영역입니다.
- new 키워드를 사용하여 생성된 인스턴스들과 배열 객체들이 여기에 할당됩니다.
- Garbage Collector가 메모리를 관리하고, 더 이상 사용되지 않는 객체를 제거하여 메모리를 회수합니다.
- 스택(Stack)
- 각 스레드마다 별도로 생성되는 메모리 영역으로, 메소드 호출과 관련된 정보를 저장합니다.
- 각 메소드 호출 시 매개변수, 지역 변수, 메소드 호출 및 복귀 주소 등을 스택 프레임(Stack Frame)에 저장합니다.
- 메소드 호출이 발생하면 해당 메소드의 스택 프레임이 생성되고, 메소드가 종료되면 해당 프레임이 스택에서 제거됩니다.
- PC 레지스터(Program Counter Register)
- 각 스레드마다 생성되며, 현재 실행 중인 JVM 명령의 주소를 가리킵니다.
- 각 스레드가 어떤 명령을 실행해야 하는지를 나타내며, 스레드 간에 공유되지 않습니다.
- 네이티브 메소드 스택(Native Method Stack)
- 자바 외부의 네이티브 코드(네이티브 메소드)를 실행하는 데 사용됩니다.
- 주로 네이티브 코드를 위한 스택 프레임들을 보관합니다.
이러한 런타임 데이터 영역은 JVM이 자바 프로그램을 실행하는 동안 필요한 데이터를 저장하고 관리하여 프로그램이 올바르게 동작할 수 있도록 합니다. 각 영역은 특정 목적에 맞게 데이터를 저장하고 스레드 간에 공유되거나 독립적으로 관리됩니다.
정리
자바의 컴파일과정과 JVM 사용 이유를 중심으로 간결하게 정리해 보겠습니다.
- JVM 사용 이유
- Java의 주요한 특징 중 하나는 플랫폼 독립성입니다.
- JVM을 사용하여 어디서든 동일한 바이트 코드를 실행함으로써, 운영체제나 하드웨어에 구애받지 않고 프로그램을 실행할 수 있습니다.
- Java 컴파일 과정
- 개발자가 작성한 .java 파일은 javac 컴파일러에 의해 JVM이 이해하는 바이트 코드로 변환됩니다.
- 변환된 바이트 코드는 JVM의 클래스 로더에 의해 메모리에 할당되고 실행됩니다.
- 실행 엔진은 인터프리터 방식과 JIT 컴파일러 방식으로 바이트 코드를 실행합니다.
- 런타임 데이터 영역은 메서드 영역과 힙 영역은 모든 스레드에서 공유되며, 스택 영역과 PC 레지스터 영역, 네이티브 메서드 영역은 각각의 스레드가 독립적으로 사용합니다.
Java는 컴파일된 바이트 코드를 JVM에서 실행함으로써 플랫폼 독립성과 안정성을 제공하며, 클래스 로더, 실행 엔진, 런타임 데이터 영역을 통해 프로그램이 실행됩니다.