JAVA | JVM(Java Virtual Machine)이란? JVM의 작동원리
JVM이란 무엇인가?
JVM은 Java 애플리케이션을 실행하기 위한 가상 기계입니다. 직역하면 '자바를 실행하기 위한 가상 컴퓨터'라고 할 수 있습니다.
이 가상 컴퓨터는 Java 프로그램이 운영체제(OS)와 독립적으로 실행될 수 있도록 도와줍니다.
Java의 플랫폼 독립성과 JVM
Java의 가장 큰 특징 중 하나는 OS에 종속되지 않는다는 점입니다. 이는 Java 프로그램이 다양한 운영체제에서 동일하게 동작할 수 있다는 의미입니다. 하지만, 이를 실현하기 위해서는 운영체제와 Java 프로그램 사이에서 중개 역할을 하는 무언가가 필요합니다.
이 역할을 수행하는 것이 바로 JVM입니다.
JVM은 CPU가 JAVA 프로그램을 인식하고 실행할 수 있도록 하는 가상 컴퓨터입니다. 실제 컴퓨터처럼 Java 애플리케이션을 실행할 수 있는 환경을 제공해줍니다.
JVM의 컴파일 과정
Java 소스코드(*.java)는 작성된 그대로는 CPU가 이해할 수 없습니다. 따라서 컴퓨터가 이해할 수 있는 기계어로 컴파일이 필요합니다.
그러나 Java는 다른 언어들과 달리 이 과정에서 운영체제에 바로 기계어를 전달하는 대신, JVM이라는 가상 머신을 거칩니다.
1. Java Compiler 는 .java 파일을 'Java 바이트코드(*.class)' 로 변환합니다.
이 컴파일러는 JDK에 포함되어 있으며, 'javac' 명령어로 실행됩니다.
2. 변환된 바이트코드는 기계어가 아니기 때문에 운영체제에서 바로 실행될 수 없습니다. 대신, 이 바이트코드는 JVM에 의해 해석되어 운영체제가 이해할 수 있는 기계어로 변환됩니다.
3. JVM은 바이트코드를 실행 가능한 형태로 변환하여, OS와 상관없이 동일하게 프로그램을 실행할 수 있게 합니다.
이로 인해 Java 프로그램은 운영체제에 종속되지 않으며, 한 번 작성된 Java 파일은 어떤 디바이스에서든 JVM 위에서 실행될 수 있습니다.
JVM의 주요 역할
1. 바이트코드 해석 및 실행
JVM의 가장 기본적인 역할은 Java 바이트코드를 해석하고 실행하는 것입니다.
Java 프로그램이 컴파일되면, 'class' 파일로 저장된 바이트코드는 OS가 직접 실행할 수 없습니다.
JVM은 이 바이트코드를 기계어(Native Code)로 변환하여, 각 운영체제에서 프로그램이 정상적으로 실행될 수 있도록 합니다.
이를 통해 Java는 한 번 작성하면 어디서나 실행된다는 플랫폼 독립성을 실현합니다.
2. 메모리 관리 및 가비지 컬렉션
JVM은 Java 프로그램이 사용하는 메모리를 관리하는 역할도 담당합니다. 특히 가비지 컬렉션(Garbage Collection)은 사용하지 않는 객체를 자동으로 메모리에서 해제하여 메모리 누수를 방지합니다. Java 7 부터는 JVM이 힙 영역을 세분화하여, 메모리 사용을 최적화하고 가비지 컬렉션의 효율을 높였습니다.
- New/Young Generation : 새로 생성된 객체가 저장되는 공간으로, Eden과 Survivor 두 영역으로 나뉩니다. 객체가 처음 생성되면 Eden에 저장되고, 가비지 컬렉션이 발생하면 살아남은 객체는 Survivor로 이동합니다.
- Old Generation : Young Generation에서 살아남아 오래된 객체들이 저장되는 공간입니다. 이 영역에서도 메모리가 부족해지면 Major GC(Full GC)가 발생합니다.
- Metaspace : JDK 8부터 도입된 이 영역은 JVM 클래스 메타 데이터를 저장하는 공간입니다. 이전에는 Permanent Generation이 이 역할을 했으나, Metaspace는 메모리 크기를 OS가 자동으로 조정할 수 있습니다.
3. 실행 엔진(Execution Engine)
JVM의 실행 엔진은 바이트코드를 실제로 기계어로 번역하고 실행하는 역할을 합니다. JVM의 실행 엔진은 크게 두 가지 방식으로 동작합니다.
* 인터프리터(Interpreter) : 바이트 코드를 한 줄씩 읽어 실행하는 방식입니다. 실행 속도는 느리지만, 즉시 실행이 가능합니다.
* JIT(Just-In-Time) 컴파일러: 인터프리터의 성능 한계를 극복하기 위해 도입된 방식으로, 자주 사용되는 바이트코드를 네이티브 코드로 변환하여 실행 속도를 크게 향상시킵니다. JIT 컴파일러는 프로그램 실행 중에 필요한 부분만 컴파일하여 성능을 최적화합니다.
4. 플랫폼 독립성 제공
JVM은 Java 프로그램이 특정 운영체제에 종속되지 않고 어디서나 실행될 수 있도록 합니다.
각 운영체제는 JVM을 통해 Java 바이트코드를 자기가 이해할 수 있는 기계어로 번역하여 실행합니다. 이로 인해 Java는 Write Once, Run Anywhere 라는 철학을 실현할 수 있습니다.
JVM의 작동 원리
JVM의 구조
JVM은 크게 클래스 로더(Class Loader), 실행 엔진(Execution Engine), 런타임 데이터 영역(Runtime DataArea)으로 구성됩니다.
1. 클래스 로더(Class Loader)
클래스 로더는 자바 바이트코드를 메모리에 적재하는 역할을 합니다. 클래스 로더는 계층적인 구조를 가지고 있으며, 여러 종류로 구분됩니다.
Bootstrap Class Loader : JVM이 시작될 때 가장 먼저 실행되며, Java API의 기본 클래스들을 로딩합니다.
Platform Class Loader : Java SE 플랫폼 API 클래스들을 로딩합니다.
System Class Loader : 사용자 정의 클래스와 애플리케이션 클래스들을 로딩합니다.
클래스 로더는 위임 모델을 따릅니다. 로딩 요청을 받은 클래스 로더는 먼저 부모 클래스 로더에게 요청을 전달하고, 부모 클래스 로더가 로딩할 수 없는 경우에만 자신이 클래스를 로딩합니다.
2. 실행 엔진(Execution Engine)
실행 엔진은 JVM의 핵심으로, 바이트 코드를 실제로 실행하는 역할을 합니다. 실행 엔진은 인터프리터(Interpreter)와 JIT(Just-In-Time) 컴파일러로 구성됩니다.
인터프리터 : 바이트 코드를 한 줄씩 해석하여 실행합니다. 바이트 코드를 즉시 실행할 수 있지만, 전체적인 성능은 느릴 수 있습니다.
JIT 컴파일러 : 인터프리터의 성능 저하 문제를 해결하기 위해 도입되었습니다. 자주 사용되는 바이트 코드를 네이티브 코드로 컴파일하여 캐시합니다. 이후에는 해당 메서드를 네이티브 코드로 실행하므로, 성능이 크게 향상됩니다.
3. 런타임 데이터 영역(Runtime Data Area)
JVM이 실행되는 동안 사용되는 메모리 영역입니다.
주요 영역
Method Area : 클래스 정보, 메서드 코드, 필드, 런타임 상수 풀 등이 저장
Heap : 객체 인스턴스가 저장되는 영역, GC에 의해 관리
Stack : 각 스레드마다 별도로 생성되는 영역으로, 메서드 호출 시 생성되는 프레임이 쌓입니다.
PC Register : 각 스레드가 현재 실행 중인 JVM 명령의 주소를 가리킵니다.
Native Method Stack : 네이티브 메서드 실행에 사용되는 메모리 영역
JVM의 실행 과정
1. 로딩(Loading)
클래스 로더가 클래스 파일(.class)을 메모리로 가져오는 과정입니다.
이 과정에서 클래스의 바이트 코드가 메서드 영역에 저장됩니다. 만약 클래스가 다른 클래스에 의존하고 있다면, 의존하는 클래스도 로딩됩니다.
2. 링크(Linking)
로딩된 클래스가 JVM에서 사용할 수 있도록 여러 과정을 거치는 단계입니다. 링크는 검증(Veriification), 준비(Preparation), 분석(Resolution)으로 나뉩니다.
* 검증 : 로딩된 바이트 코드가 JVM 명세를 따르고 있는지 검증하는 과정
* 준비 : 클래스의 정적 필드를 기본값으로 초기화합니다.
* 분석 : 런타임 상수 풀의 심볼릭 레퍼런스를 실제 메모리 주소로 변환합니다.
3. 초기화(Initialization)
클래스의 정적 필드들이 초기화되며, static 초기화 블록이 실행됩니다. 초기화 단계는 클래스가 처음으로 사용될 때 한 번만 실행됩니다.
JVM 종료
JVM이 종료되는 시점은 모든 사용자 스레드가 종료되거나, 특정 종료 메서드가 호출될 때입니다.
JVM이 종료되면 메모리에서 모든 자원이 해제되고 프로그램이 완전히 종료됩니다.
출처