Java로 개발을 시작하기도 전부터 'JVM에 대해서는 잘 알고 있어야 한다'는 이야기를 들었었다.
특정 운영체제 종속적이지 않고, 직접 메모리를 관리해주는 등 JVM이 중요한 이유도 함께 들었던 기억이 있다.
오늘 드디어 이 JVM에 대해 간단히 정리를 해본다.
JVM(Java Virtual Machine)
JVM은 Java Virtual Machine, 즉 자바 가상 머신의 약자로 자바 프로그램 런타임 환경을 제공하는 소프트웨어이다.
자바 애플리케이션을 클래스 로더를 통해 읽어 들여, 자바 API와 함께 실행하는 역할을 한다.
Java와 OS 사이에서 중개자 역할을 수행해 Java가 OS에 구애받지 않고 실행되도록 한다.
또한, Garbage Collection, 즉 프로그램 메모리 관리도 하며 최적화도 수행한다.
Java 프로그램 실행 순서
- Java 프로그램이 실행되면 JVM은 OS로부터 해당 프로그램이 필요로 하는 메모리를 할당받는다. JVM은 할당받은 메모리를 용도에 따라 여러 영역으로 나누어 관리한다.
- 자바 컴파일러(javac)가 자바 소스 코드(.java)를 읽어 들여 자바 바이트 코드(.class)로 변환시킨다.
- Class Loader를 통해 자바 바이트 코드를 JVM Runtime Data Areas(메모리 영역)로 로딩한다.
- 로딩된 바이트 코드들은 Execution Engine을 통해 해석된다.
- 해석된 바이트 코드는 JVM Runtime Data Areas(메모리 영역)에 배치되어 실질적인 수행이 이루어진다. 이러한 실행과정에서 JVM은 필요에 따라 Garbage Collection과 같은 메모리 관리 작업을 수행한다.
JVM 구성
Class Loader(클래스 로더)
Java는 동적으로 클래스(.class 파일)를 읽어오므로, 프로그램이 실행 중인 Runtime에서야 모든 코드가 JVM과 연결된다.
이렇게 동적으로 클래스를 로딩해주는 역할을 하는 것이 바로 클래스 로더(Class Loader)다.
Java에서 소스 코드를 작성하면 .java파일이 생성되고 이것을 자바 컴파일러가 컴파일하면 .class파일이 생성되는데 클래스 로더는 .class 파일을 묶어서 JVM이 운영체제로부터 할당받은 메모리 영역인 Runtime Data Area로 로딩한다.
Execution Engine(실행 엔진)
클래스를 실행시키는 역할을 한다.
Class Loader가 JVM 내의 Runtime Data Area에 바이트 코드(.class)를 배치시키면 Execution Engine(실행 엔진)에 의해 실행된다.
자바 바이트 코드는 기계가 바로 수행할 수 있는 형태로 기술되어 있지 않다.
그렇기 때문에 실행 엔진은 이 바이트 코드를 실제로 JVM 내부에서 기계가 실행할 수 있는 형태로 변경한다.
이 때는 2가지 방식을 이용한다.
Interpreter
바이트 코드 명령어를 하나씩 읽어서 해석하고 실행하는 방식이다.
전체적인 실행 속도가 느리다는 단점이 있다.
JIT(Just-In-Time) Compiler
인터프리터의 단점을 보완하기 위해 도입되었다.
인터프리터 방식으로 실행하다가 적절한 시점에 바이트 코드 전체를 컴파일해 Native Code로 변경하고, 이후에는 더 이상 인터프리팅하지 않고 Native Code로 직접 실행하는 방식이다.
Native Code는 캐시에 보관해 이후 컴파일 시 바뀐 부분만 컴파일하고, 나머지는 캐싱된 코드를 수행하기 때문에 한 번 컴파일된 코드는 빠르게 수행된다.
바이트 코드를 Native Code로 변환하는 데에도 비용이 소요되므로 JVM은 모든 코드를 JIT 컴파일러 방식으로 실행하지 않고, 인터프리터 방식을 사용하다가 일정 기준이 넘어가면 JIT 컴파일 방식으로 명령어를 실행한다.
Garbage Collector(가비지 컬렉터)
JVM은 가비지 컬렉터(Garbage Collector)를 이용해 더는 사용하지 않는 메모리를 자동으로 회수한다.
Heap 메모리 영역에 생성(적재)된 객체들 중에 참조되지 않은 객체들을 탐색 후 제거하는 역할을 한다.
GC역할을 수행하는 스레드를 제외한 나머지 모든 스레드들은 일시 정지 상태가 된다.
Runtime Data Area
프로그램을 수행하기 위해 OS에서 할당 받은 메모리 공간으로 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역이다.
모든 스레드(Thread)가 공유해서 사용(GC의 대상)
- 힙 영역(Heap Area)
- 메서드 영역(Method Area)
⇒ JVM이 시작될 때 생성되고, JVM이 종료되면 해제된다.
스레드(Thread)마다 독립적으로 하나씩 생성
- 스택 영역(Stack Area)
- PC 레지스터(PC Register)
- 네이티브 메서드 스택(Native Method Stack)
⇒ 스레드가 생성될 때 생성되며, 스레드가 종료되면 해제된다.
힙 영역(Heap Area)
Runtime에 동적으로 할당되는 데이터가 저장되는 영역이다.
new 키워드로 생성된 객체와 배열이 생성되면 힙 영역에 저장된다.
할당된 객체는 직접 해제가 불가능하며, 주기적으로 GC가 제거한다.
메서드 영역(Method Area)
클래스 멤버 변수의 이름, 데이터 타입, 접근 제어자 정보와 같은 필드 정보들과 메서드 정보 Constant Pool, static 변수, final class 등이 생성되는 영역이다.
스택 영역(Stack Area)
지역변수, 매개변수(파라미터), 리턴 값, 연산에 사용되는 임시 값 등을 저장하는 영역이다.
PC 레지스터(PC Register)
스레드(Thread)가 생성될 때마다 각각 생성되는 영역으로 현재 스레드가 어떤 명령어로 실행되어야 할지 스레드가 실행되는 부분의 주소와 명령을 저장하고 있는 영역이다.
네이티브 메서드 스택(Native Method Stack)
자바 이외의 언어(C, C++, 어셈블리 등)로 작성된 코드를 실행할 때, Native Method Stack이 할당되며, 일반적인 C 스택을 사용한다.
'Java' 카테고리의 다른 글
[Java] Integer ArrayList을 int 배열로 변환하는 방법 (1) | 2023.09.24 |
---|---|
[Java/DB] ORM이란? MyBatis와 JPA 비교 및 예제 (0) | 2023.08.25 |
[Java] 코드 특정 구간의 실행 시간 구하기 (0) | 2023.06.14 |
[Java] HashMap에 특정 key가 존재하는지 확인하기 (0) | 2023.04.17 |
[Java] new ArrayList<>() 와 Arrays.asList() 차이점 및 비교 (0) | 2022.12.26 |