자바 프로그램을 실행하게 해주는 가상 머신 ( 환경 )
왜 JVM을 사용할까
운영체제에 상관 없이 실행할 수 있도록 해주고, GC와 같은 자동 메모리 정리 기능을 제공해주며, 자바, 코틀린, 스칼라 등의 다양한 언어를 사용할 수 있기 때문입니다.

컴파일 → 클래스 로딩 → 메모리 할당 → 바이트 코드 해석 및 실행 → 가비지 컬렉션 → 예외 처리 → 프로그램 종료
자바 프로그램을 실행하면 JVM은 OS로부터 메모리를 할당받습니다.
컴파일

자바 프로그램을 실행시키는 시점에 자바 소스 코드(.java)를 컴파일러가 바이트 코드(.class)로 변환합니다.
바이트 코드로 변환하는 것 : JVM이 실행할 수 있는 언어로 변환되는 과정
Java Source File(java) → (Java Complier) → Java Byte Code(class)
클래스 로딩

JVM은 컴파일 된 바이트 코드를 읽어 메모리에 로드하며, 이는 런타임 시점에 이루어집니다.
바이트 코드는 Class Loader에 의해 JVM의 Runtime Data Area로 로딩됩니다.
클래스 자체( 메타데이터, 상수 풀 등 )는 이 로딩 과정에서 메모리에 올라가지만, 인스턴스 변수나 객체의 실제 데이터는 클래스 로딩 시점에 올라가지 않습니다.
객체의 생성은 new 키워드를 통해 요청될 때 이루어지며, 실제 인스턴스 데이터는 Heap 영역에 할당됩니다.
메모리 로딩
JVM이 자바 프로그램 실행에 필요한 클래스와 관련 데이터를 메모리로 적재하는 과정입니다.
이를 통해 프로그램 실행 시 필요한 코드와 정보가 메모리에 준비되어, 실제로 실행될 수 있는 환경이 갖춰지게 됩니다.
메모리 할당

해당 과정은 메모리에 로딩된 클래스 정보나 코드들을 실행하기 위해, JVM이 Runtime Data Area 내 적절한 공간을 배정하고 초기화하는 과정입니다. ( 메모리를 할당하는 과정 )
특히 메모리 할당은 객체 생성 시점에서 가장 중요한 역할을 합니다.
객체나 배열이 생성될 때 JVM은 해당 객체의 크기를 계산한 뒤, Heap 영역에 필요한 메모리를 확보하여 할당합니다.
바이트 코드 해석 및 실행

JVM은 이전에 생성한 바이트 코드를 읽어 운영체제가 이해할 수 있는 네이티브 코드로 변환하여 실행합니다.
이때, 실행 엔진인 Execution Engine에서 실행됩니다.
Interpreter에서의 바이트 코드 수행 및 실행 과정
Interpreter : 바이트 코드를 명령어 단위로 하나씩 해석하여 실행하는 방식 ( 네이티브 코드로 변환하지 않음 )
⇒ 초기 실행 속도는 빠르지만, 반복 실행되는 코드에서는 비효율적입니다.
[ 수행 과정 ]
이전에 생성하고 할당된 메모리를 기반으로 바이트 코드를 읽어옴
인터프리터를 통해 바이트 코드를 한 줄씩 읽고 해석
바이트 코드를 해석하는 동안 인터프리터는 필요한 메모리 및 리소스를 할당하고 해당 바이트 코드에 따라 실행
바이트 코드가 실행되는 동안 인터프리터는 필요한 경우에 따라 라이브러리 및 다른 클래스를 로드하고 사용
인터프리터는 바이트 코드를 순차적으로 해석하면서 프로그램을 실행
JIT( Just-In-Time )의 바이트 코드 수행 및 실행 과정
JIT : Interpreter의 단점을 보완한 것으로, 자주 실행되는 코드를 네이티브 코드로 변환하여 캐싱 후 재사용합니다.
⇒ 캐싱하여 반복 사용하므로 속도가 매우 빠릅니다.
[ 수행 과정 ]
이전에 생성하고 할당된 메모리를 기반으로 바이트 코드를 읽어옴
JIT 컴파일러는 실행 중인 프로그램을 모니터링하면서 빈번하게 실행되는 코드 블록을 식별
해당 코드 블록은 인터프리터에 의해 실행될 때마다 JIT 컴파일러에 의해 네이티브 코드로 변환됨
JIT 컴파일러는 변환된 네이티브 코드를 캐시에 저장
이후 동일한 코드 블록이 다시 호출되면, 캐시에 저장된 네이티브 코드를 실행하여 인터프리터에 비해 훨씬 빠른 실행 속도를 제공함
Native Method Interface & Libaray
Java는 순수 Java 코드 외에도 C/C++로 작성된 네이티브 메서드를 호출할 수 있는데, 이를 가능하게 하는 것이 JNI( Java Native Interface )
Native Method Interface는 네이티브 코드와 JVM 사이의 통신을 담당하고, Native Method Library는 실제 C/C++로 구현된 네이티브 함수들이 포함된 라이브러리입니다.
가비지 컬렉션
JVM에서 더 이상 사용되지 않은 객체를 자동으로 감시하고 메모리에서 자동으로 해제하는 역할을 수행, 이를 통해 메모리 관리를 최적화할 수 있음
[ 동작 과정 ]
Marking ( 표시 ) : 가비지 컬렉터는 모든 객체를 스캔하고 참조되는 객체들을 표시
Sweeping ( 정리 ): 가비지 컬렉터는 표시되지 않은 객체들을 메모리에서 제거,
Compacting ( 압축 ): 가비지 컬렉터는 메모리에서 제거된 객체들로 인해 생긴 빈 공간을 모아서 압축하며, 이를 통해 메모리 공간을 확보
⇒ 단편화를 방지
예외 처리

프로그램 실행 도중 발생하는 예기치 않은 상황으로, 예외가 발생하면 JVM은 해당 예외를 적절히 처리하고 예외 상황에 대한 적절한 조치를 수행합니다.
JVM은 예외 처리를 위해 예외 핸들러를 사용하며, 예외가 발생했을 때 해당 예외를 처리할 수 있는 예외 핸들러를 찾아 처리
[ 예외 처리 과정 ]
프로그램이 실행 중에 예외가 발생 → 예상치 못한 상황 또는 오류가 발생한 것을 의미
예외 객체 생성
예외가 발생하면 JVM은 해당 예외를 나타내는 예외 객체를 생성하고, 이 객체는 예외의 유형, 메시지 및 예외가 발생한 위치 정보를 포함함
JVM은 예외를 적절히 처리하기 위해 예외 처리기( Exception Handler )를 찾고, 예외 처리기는 예외를 처리하는 코드 블록으로, 예외를 처리하는 방법을 정의함
예외 처리기가 발견되지 않거나 처리하지 못한 경우, 예외는 현재 실행 중인 메서드에서 호출한 메서드로 전파되며, 이 과정은 메소드 호출 스택을 따라 위로 올라감
예외는 예외를 전파한 메서드의 호출자에게 전달되며, JVM은 호출자 메서드에서 예외 처리기를 찾습니다. 이 과정은 호출자 메서드의 호출자로 계속해서 진행됨
예외 처리기가 발견되면 예외 처리기의 코드 블록이 실행되며, 예외 처리기는 예외를 처리하거나 예외를 다시 던지는 등의 작업을 수행할 수 있음
예외 처리기가 예외를 다시 던지는 경우, 예외는 다시 호출자로 전파되며, 이 과정은 호출자 메소드의 호출자로 계속해서 진행됨 이렇게 예외가 여러 단계로 전파되는 구조를 예외 체인이라고 함
프로그램 종료

프로그램 실행이 완료되거나 명시적으로 종료되면 JVM은 사용한 메모리를 반환하고 자원을 정리합니다.

자바 프로그램이 실행될 때 사용되는 메모리 공간을 의미하며, 각각의 영역은 특정한 목적과 역할을 가지고 있습니다.
왜 메모리 영역을 Runtime Data Area라고 부를까
자바 프로그램을 실행하는 동안 동적으로 할당하고 사용되는 데이터와 정보를 저장하며 프로그램의 런타임 동안 필요한 데이터를 보관하고 관리하는 역할까지 수행하기 때문
JVM 메모리 영역의 구조

스레드들은 Heap, Method Area를 공유하고, Jvm stack, PC Register, Native Method Stack을 각각 가집니다.

| 메모리 영역 | 저장 요소 | 스레드 공유 여부 | 생성 시점 | 소멸 시점 | 가비지 컬렉션 대상 |
|---|---|---|---|---|---|
| Method Area | 메소드, 클래스 정보 | 모두 공유 | JVM 실행 시 ( 클래스 로딩 시 ) | JVM 종료 시 | 대상 아님 |
| 메타스페이스 영역(java 8 ~) | 메서드, 클래스 정보+ 클래스 메타데이터 | 모두 공유 | JVM 실행 시 | JVM 종료 시 | 대상 아님 |
| Heap | 객체, 인스턴스 변수, 배열 정보 | 모두 공유 | 프로그램 실행 시 ( new 키워드로 객체 생성 시 ) | 프로그램 종료 시 | 대상 |
| Stack | 메서드 호출 시 사용한 메서드 인자, 지역 변수, 리턴 값, 임시 값 정보 | 스레드 별로 생성, 공유하지 않음 | 스레드 시작 시 ( 메서드 호출 시 ) | 스레드 실행 종료 시 ( 메서드 종료 시 ) | 대상 아님 |
| PC Register | 실행중인 명령어 주소 정보 | 스레드 별로 생성, 공유하지 않음 | 스레드 시작 시 | 스레드 실행 종료 시 | 대상 아님 |
| Native Method Stacks | 네이티브 메서드의 정보 | 스레드 별로 생성, 공유하지 않음 | 네이티브 메서드 호출 시 | 네이트브 메서드 실행 완료 시 | 대상 |
Method Area
클래스 정보를 저장하는 메모리 공간 ( JVM이 클래스를 로드해두고 공유하기 위한 공간 )
클래스 이름, 메서드 정보, 필드 정보, 인터페이스 정보 등등의 클래스 정보와 static 변수, 상수 풀 등을 저장합니다.
정적 여부와 상관없이 클래스의 모든 메서드와 필드의 구조 정보는 메서드 영역에 저장되고, 실제 값은 static이면 메서드 영역에, 그렇지 않다면 Heap 영역에 저장됩니다.
해당 영역은 JVM이 실행될 때 생성되며 JVM이 종료될 때 메모리에서 해제됩니다.
정적 변수는 클래스 수준의 변수로 메서드 영역에 저장되어 모든 인스턴스가 공유할 수 있습니다.
메타 데이터란
데이터에 대한 데이터로서, 데이터를 설명하고 정의하고 구조화하는 정보를 의미합니다
힙 영역
프로그램에서 사용되는 대부분의 객체가 저장되는 공간입니다.
( new 로 생성한 모든 객체, 배열, 내부적으로 사용되는 객체 등등 )
해당 영역은 프로그램이 Runtime 때 생성되며 프로그램이 종료될 때 메모리에서 해제됩니다.
GC가 동작하는 영역입니다.
객체의 저장 방법
참조형 변수의 변수명은 스택에 저장되며, 이 참조 변수는 힙 영역의 객체 주소를 값으로 가집니다.
힙 영역의 해당 주소 값에 실제 참조 변수의 값이 저장되며, 이 주소를 통해 힙 영역의 실제 객체 데이터에 접근할 수 있습니다.
힙 영역의 구성 요소

Young Genration : 새로 생성된 객체들이 할당되는 공간
Old Generation : 일정 시간이 지나거나 오래 살아남은 객체들이 이동하는 공간
스택 영역 - 스레드별 독립적으로 존재
메서드 실행을 위한 스택 프레임을 저장하는 영역입니다.
( 지역 변수, 매개변수, 연산 시의 임시 값, 리턴값 주소 등등 )
해당 영역은 스레드가 시작될 때 생성되고, 스레드의 실행이 종료될 때 메모리에서 해제됩니다.
해당 영역은 각 스레드마다 별도의 스택 영역이 생성됨에 따라 모든 스레드와는 공유가 되지 않고 자신만의 영역을 갖습니다.
메서드가 호출될 때마다 스택 프레임이 생성되며 메서드의 매개변수, 지역 변수, 리턴 주소 등이 후입선출 구조로 동작하여 스택 프레임에 저장됩니다.
어떤 메서드가 호출되면, 그 메서드를 실행 중인 스레드가 해당 메서드 정보를 자기 스택에 저장하고 순차적으로 실행
메소드의 실행이 종료되면 해당 스택 프레임은 제거됨
[Thread A]
└─ [Stack A]
├── main()
├── methodA()
└── methodB()
[Thread B]
└─ [Stack B]
├── run()
└── task()
스택 프레임 : 메소드가 호출될 때 스레드의 스택에 생성되는 메모리 블록
스택 영역의 후입선출 구조 : 가장 최근에 호출된 메서드가 가장 먼저 실행을 완료하고 스택에서 제거됨
PC 레지스터
현재 스레드가 실행 중인 바이트코드 명령의 위치를 저장하는 영역입니다.
⇒ 현재 어느 명령을 실행하고 있는지를 가르키는 주소 1개를 저장합니다.
해당 영역은 스레드가 시작될 때 초기화 되며, 스레드가 종료되면 메모리에서 해제됩니다.
스레드마다 독립적으로 관리되며, 각 스레드는 자신의 PC 레지스터를 가지고 있고, 이를 통해 다중 스레드 환경에서 동시에 여러 명령어를 실행할 수 있음습니다.
메서드를 실행하다가 다른 스레드로 전환되면 현재 실행 중이던 명령어의 주소가 해당 스레드의 PC 레지스터에 저장되고, 이후 다시 해당 스레드로 전환되면 저장된 PC 레지스터 값을 기반으로 중단된 지점부터 명령어 실행을 재개
네이티브 메서드 스택 영역
C, C++, 어셈블리 등의 네이티브 메서드( JNI ) 실행을 위한 스택 영역입니다.
( 네이티브 코드 실행 스택, C/C++ 함수의 지역 변수, JIT 호출 정보 등을 저장 )
해당 영역은 네이티브 메소드 호출 시 생성되며 네이티브 메서드의 실행이 완료되면 메모리에서 해제됨
네이티브 메서드의 호출과 반환에 필요한 데이터를 저장하며, 이 영역은 스택 프레임이라고도 불리는 작은 메모리 블록들로 구성되어 있으며, 네이티브 메소드 호출 시 스택 프레임이 생성되고, 메소드의 실행이 완료되면 스택 프레임이 제거됨
JVM이 자바 프로그램에서 네이티브 메서드를 실행하는 데 필요한 자원을 제어하고 최적화하는 데 사용되며, 각 스레드마다 독립적으로 생성되고, 네이티브 메소드 호출이 발생할 때 해당 스레드의 스택 영역에 스택 프레임이 생성됨