JVM (Java Virual Machine)
JVM 은 'Java Virtual Machine' 을 줄인 것으로 직역하자면 '자바를 실행하기 위한 가상 컴퓨터' 정도로 이해하면 됩니다. 자바로 작성된 애플리케이션은 모두 JVM 에서만 실행됩니다. (Kotlin, Scala 와 같이 JVM 기반의 언어도 동일)
일반 애플리케이션 코드는 OS만 거치고 하드웨어로 전달되는데 비해 Java는 JVM을 한번 더 거쳐서 전달되며 기계어로 완전히 컴파일되지 않고 실행 시에 해석되기 때문에 속도가 느리다는 단점이 있습니다. 그러나 요즘엔 바이트 코드(JVM이 이해하는 컴파일된 Java 코드)를 하드웨어의 기계어로 바로 변환해주는 JIT 컴파일러와 향상된 최적화 기술이 적용되어서 속도의 격차를 많이 줄였습니다.
위 그림에서 볼 수 있듯이 일반 애플리케이션의 경우 OS와 바로 맞닿아 있기 때문에 OS 종속적입니다. 그래서 OS에 맞게 변경해야 합니다. 반면에 Java 는 JVM하고만 상호작용하기 때문에 OS에 독립적이며 변경없이 어디서나 실행 가능합니다. 단, JVM은 OS에 종속적이기 때문에 해당 OS에서 실행 가능한 JVM이 필요합니다.
그래서 일반적으로 많이 사용되는 주요 OS용 JVM은 이미 제공되고 있으며 이러한 특징으로 Java 의 유명한 슬로건인 "Write once, run anywhere.(한번 작성하면 어디서든 실행된다.)" 가 가능하게 됩니다.
요약
- Java 로 작성된 애플리케이션은 JVM 에서만 실행 가능하다.
- JVM 을 거쳐서 하드웨어로 전달되기 때문에 속도가 느리다는 단점이 있다.
- JIT 컴파일러와 향상된 최적화 기술이 적용되어 느린 속도를 많이 개선시켰다.
- Java 로 작성된 애플리케이션은 JVM 하고만 상호작용하면 되기 때문에 OS 에 독립적일 수 있다.
- JVM 은 OS 에 종속적이기 때문에 OS 에 맞는 JVM 이 별도로 필요하다.
JVM 의 동작 방식
JVM 의 역할은 자바 애플리케이션을 클래스 로더를 통해 읽어 자바 API와 함께 실행하는 것입니다.
- 자바 프로그램을 실행하면 JVM은 OS로부터 메모리를 할당 받는다.
- 자바 컴파일러(javac)가 자바 소스코드(.java)를 자바 바이트 코드(.class)로 컴파일 한다.
- Class Loader는 동적 로딩을 통해 필요한 클래스들을 로딩 및 링크하여 Runtime Data Area(실질적인 메모리 관리 영역)에 올린다.
- Runtime Data Area에 로딩된 바이트 코드는 Execution Engine을 통해 해석된다.
- 이 과정에서 Execution Engine에 의해 Garbage Collector의 작동과 Thread 동기화가 이루어진다.
JVM 의 구조
다음은 Class Loader - Runtime Data Area - Execution Engine 부분을 상세화한 그림입니다.
- 클래스 로더 (Class Loader)
- 실행 엔진 (Execution Engine)
- 인터프리터 (Interpreter)
- JIT 컴파일러 (Just-in-Time)
- 가비지 콜렉터 (Garbage Collector)
- 런타임 데이터 영역 (Runtime Data Area)
- 메소드 영역
- 힙 영역
- PC Register
- 스택 영역
- 네이티브 메소드 영역
- 네이티브 메소드 인터페이스 (JNI - Native Method Interface)
- 네이티브 메소드 라이브러리 (Native Method Library)
클래스로더 (Class Loader)
클래스로더는 클래스 파일(*.class)을 동적으로 JVM에 로드하고 링크를 통해 메모리 영역에 배치하는 작업을 수행하는 모듈입니다.
클래스를 메모리에 올리는 로딩은 한번에 모든 클래스를 올리지 않고 Runtime 시에 동적으로 어플리케이션에서 필요한 경우에 호출하여 메모리에 적재합니다.
실행 엔진 (Execution Engine)
클래스 로더를 통해 JVM 내의 Runtime Data Area 에 배치된 바이트 코드들을 명령어 단위로 읽어서 실행합니다. 자바 바이트 코드를 JVM 내부에서 기계가 실행할 수 있는 형태로 변환합니다.
인터프리터 (Interpreter)
자바 바이트 코드를 명령어 단위로 한 줄씩 읽어서 실행하는 방식입니다.
JIT (Just In Time)
인터프리터 방식의 단점을 보완하기 위해 도입된 방식입니다. 인터프리터 방식으로 실행하다가 적절한 시점에 바이트 코드 전체를 컴파일하여 어셈블러와 같은 네이티브 코드로 변환하고 이후에는 더 이상 인터프리팅하지 않고 네이티브 코드로 직접 실행하는 방식입니다.
네이티브 코드는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 빠르게 수행하게 됩니다. 하지만 JIT 컴파일러가 컴파일하는 과정은 바이트 코드를 인터프리팅하는 것보다 오래 걸리기 때문에 한 번만 실행되는 코드라면 인터프리팅하는 것이 유리합니다. 따라서 JVM들은 내부적으로 해당 메서드가 얼마나 자주 수행되는지 체크하고, 일정 기준을 넘을 때에만 컴파일을 수행합니다.
Garbage Collector
힙 메모리 영역에 생성된 객체들 중에서 참조되지 않은 객체들을 탐색 후 제거하는 역할을 합니다. 이때, GC가 수행되는 시간은 언제인지 정확히 알 수 없습니다.
Runtime Data Area
OS로부터 할당 받은 JVM의 메모리 영역으로 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역입니다.
1. PC Register
스레드가 시작될 때 생성되는 공간으로 스레드마다 하나씩 존재합니다. 스레드가 어떤 부분을 어떤 명령으로 실행해야 하는지를 기록하는 부분으로 현재 수행 중인 JVM 명령의 주소 값을 가집니다.
2. JVM Stack
메소드 호출 시마다 각각의 스택 프레임(해당 메서드만을 위한 공간)이 생성되고 메소드 수행이 끝나면 프레임별로 삭제합니다. 메소드 안에서 사용되는 값들을 저장하고 호출된 메소드의 매개변수, 지역변수, 반환 값 및 연산 시 일어나는 값들을 임시로 저장합니다.
3. Native Method Stack
자바 외 언어로 작성된 네이티브 코드를 위한 메모리 영역입니다. JNI(Java Native Interface)를 통해 바이트 코드로 전환하여 저장하게 됩니다.
4. Method Area
모든 스레드가 공유하는 메모리 영역으로 클래스, 인터페이스, 메소드, 필드, Static 변수 등의 바이트 코드를 관리합니다.
5. Heap
모든 스레드가 공유하며 new 키워드로 생성된 객체와 배열이 생성되는 영역입니다. 또한 메소드 영역에 로드된 클래스만 생성이 가능하고 Garbage Collector 가 참조되지 않는 메모리를 확인하고 제거하는 영역입니다.
'Java' 카테고리의 다른 글
Equals와 HashCode 는 왜 같이 재정의해야 할까? (1) | 2024.11.15 |
---|---|
Overloading & Overriding (2) | 2024.11.04 |
POI Excel 인쇄 영역, 페이지 나누기 설정 (0) | 2024.11.04 |
함수형 인터페이스(Functional Interface) (1) | 2024.11.03 |
댓글