JVM(Java Virtual Machine) 이란
JVM(Java Virtual Machine)은 자바 프로그램 실행환경을 만들어 주는 소프트웨어이다. 자바 코드를 컴파일하여 .class 바이트 코드로 만든 후 실행시키면 이 코드가 JVM 환경에서 실행된다. 이렇듯 JVM이 자바와 OS 사이에서 중개자 역할을 하기 때문에 자바는 OS에 구애 받지 않는다.
JVM의 구성 요소
Class loader System
클래스 로더는 컴파일 된 바이트코드(*.class)를 실행시점(RunTime)에 읽어들여서 메모리(Runtime Data Area)에 적절하게 배치한다. 작업은 로딩 -> 링크 -> 초기화 순서로 일어난다.
- 로딩 : 클래스 로더가 .class 파일을 읽고 그 내용에 따라 적절한 바이너리 데이터를 만들고 메서드 영역에 저장함. 로딩이 끝나면 해당 클래스 타입의 Class 객체를 생성하여 “힙" 영역에 저장.
- 링크 : Verify(확인), Prepare(준비), Resolve(해결) 세 단계로 나누어져 있다.
- 초기화 : static 변수의 값을 할당한다. static 블럭은 이때 실행된다.
메모리(Runtime Data Areas)
JVM이 프로세스로써 수행되기 위해 OS로부터 할당 받는 메모리 영역이다. 목적에 따라 크게 5가지 블럭으로 나뉜다.
- 메소드 영역(Method Area) – 모든 스레드가 공유하는 영역
- 런타임 상수 풀(Runtime Constant Pool), 필드 데이터, 메소드 데이터가 저장되는 공간
- 보다 간단히 말해 클래스의 정보(클래스 이름, 부모 클래스 이름, 인터페이스, 메소드, 변수, 접근 제어자)가 저장되는 공간이라고 할 수 있다.
* 런타임 상수 풀(Runtime Constant Pool)
클래스와 인터페이스의 상수 데이터를 저장하는 동적 구조. 리터럴 상수나 심볼릭 참조(클래스와 인터페이스 이름, 필드 이름, 메소드 이름 등) 정보가 저장된다.
* 심볼릭 참조(Symbolic Reference)
특정 객체를 참조할 때 Memory Address를 직접 참조하는 게 아니라 객체의 이름으로 참조하는 방식을 의미한다. - 힙 영역(Heap) – 모든 스레드가 공유하는 영역
- 프로그램을 실행하면서 생성된 모든 객체가 저장되는 공간.
- 인스턴스화 된 모든 클래스와 배열이 저장되는 공간이다.
- 힙에 할당된 메모리 회수는 무조건 가비지 컬렉터(Garbage Collector)에 의해서만 회수가 가능하다. - 스택 영역(Java Stack) – 각 스레드 별로 존재하는 영역
- 메소드를 실행하기 위한 정보들이 저장되는 공간이다
- 메소드가 호출될 때마다 스택 프레임이 스택에 push 되고 메소드 실행이 완료되면 pop된다.
- 우리가 흔히 재귀 함수를 잘못 사용하여 무한정 메소드가 재 생성될 때 Stack Overflow 에러가 발생하는 이유가 이러한 스택 영역의 특성 때문이다.
* 스택 프레임(Stack Frame)
메소드 호출과 실행에 필요한 정보를 담고 있는 구조체이다. 스택 프레임 안에 들어있는 정보는 다음과 같다.
1) 로컬 변수 배열(Local Variable Array) : 메소드의 파라미터와 로컬 변수가 저장되는 배열. 파라미터와 변수는 인덱스를 통해 접근할 수 있으며 이 배열의 크기는 컴파일 타임에 결정된다.
2) 연산 스택(Operand Stack) : 메소드 실행 도중 발생하는 연산의 중간 결과가 저장된다. 산술 연산이나 메소드 호출의 결과 값 등이 이 스택에 저장된다.
3) 프레임 데이터(Frame Data) : 메소드 호출과 관련된 추가 정보가 저장된다. 동적 링크 정보, 메소드를 호출한 위치의 반환 주소 등이 여기에 포함된다. - PC 레지스터 영역(PC Registers) – 각 스레드 별로 존재하는 영역
- 스레드가 현재 실행하고 있는 부분의 주소를 저장한다. OS는 PC(Program Counter) Register를 참고하여 CPU 스케줄링 시 해당 스레드가 다음에 어떤 명령어를 수행해야 하는지 알 수 있다. - 네이티브 메소드 영역(Native Method Stacks) – 각 스레드 별로 존재하는 영역
- 자바 외 언어(C/ C++ 등)로 작성된 네이티브 코드를 위한 메모리 영역이다. 각 언어에 맞게 Stack이 생성된다.
Execution Engine
클래스 로더가 Runtime Data Areas에 불러온 바이트코드를 실행한다. 바이트코드를 실행할 때 기계어로 변경해 명령어 단위로 실행하는데, 1바이트의 OpCode와 피연산자로 구성된다.
Execution Engine은 2가지 방식으로 명령어를 실행한다.
- Interpreter : 명령을 순차적으로 읽으며 실행한다. 파일 이름을 인자로 받으며, main 함수를 포함해야한다.
- JIT (Just In Time) Compiler : Interpreter의 느린 단점을 없애고자 나온 컴파일러이다. JIT Compiler는 유사한 바이트코드를 매번 다시 컴파일하지 않고, 캐싱해둬서 컴파일에 필요한 총 시간을 단축한다.
GC(Garbage Collector)
힙 영역에서 참조되지 않는 오브젝트를 제거하는 역할을 한다.
네이티브 메소드 처리
- JNI(Java Native Interface) : 자바 애플리케이션에서 C, C++, 어셈블리로 작성된 Native 키워드를 사용한 함수를 사용할 수 있는 방법 제공
- 네이티브 메소드 라이브러리 : C, C++로 작성 된 라이브러리
SpringBoot 코드가 실행될 때 JVM 메모리 영역에 어떻게 저장되는가
아래와 같은 코드가 있다고 가정하자.
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
@RestController
class HelloController {
@GetMapping("/hello")
public String sayHello() {
int testNum = 10;
System.out.println(testNum);
String testStringOne = "test1";
System.out.println(testStringOne);
String testStringTwo = new String("test2");
System.out.println(testStringTwo);
Map<String, String> map = new HashMap<>();
return "Hello, World!";
}
}
위 코드가 실행될 때 각 코드는 JVM 메모리에 다음과 같이 적재된다.
1. 메소드 영역(Method Area)
- MyApp 클래스와 HelloController 클래스, 그리고 이들 클래스에 포함된 메소드의 메타데이터
- Spring Boot 애플리케이션을 시작하는 데 필요한 메타데이터와 스프링의 내부 클래스들도 여기에 로드된다.
- @SpringBootApplication, @RestController, @GetMapping 등의 어노테이션 정보도 이 영역에 저장된다.
2. 힙 영역(Heap)
- 스프링 프레임워크에 의해 생성되는 singleton bean인 MyApp과 HelloController 인스턴스
- new 로 생성된 String 인스턴스와 map 인스턴스
- String 리터럴인 "test1"과 "Hello, World!"도 힙의 일부인 문자열 상수 풀(String Constant Pool) 에 저장된다.
- 또한 스프링 어플리케이션 컨텍스트(Application Context)와 같이 런타임 중 생성되는 객체들도 힙 영역에 위치한다.
3. 스택 영역(Java Stack)
- main 메소드와 sayHello 메소드가 호출될 때, 각 스레드의 스택 영역에는 이 메소드들을 위한 스택 프레임이 생성된다.
- 각 스레드 별로 실행 중인 메소드의 지역 변수, 매개 변수, 리턴 값이 스택 프레임 내에 저장된다.
- 여기에서는 지역변수인 testNum, testStringOne, testStringTwo, map 이 저장된다.
- 또한 sayHello 메소드에서 반환되는 문자열 "Hello, World!"도 이 스택 프레임 내에 임시로 저장된다.
4. PC 레지스터 영역(PC Registers)
- 현재 실행 중인 스레드의 JVM 명령어 주소가 저장된다. 예를 들어, main 메소드나 sayHello 메소드 내의 명령어 실행 시 PC 레지스터가 이를 가리킨다.
5. 네이티브 메소드 영역(Native Method Stacks)
- 만약 sayHello 메소드에서 Java가 아닌 다른 언어로 작성된 코드를 호출한다면 그 호출은 네이티브 메소드 스택을 사용한다. 단 위의 예시 코드에서는 Java 네이티브 메소드 호출이 없으므로 이 영역은 사용되지 않는다.
'Java' 카테고리의 다른 글
BigDecimal (0) | 2024.05.20 |
---|---|
String과 String Constant Pool (0) | 2024.03.26 |
상수(Constant)와 리터럴(Literal) (0) | 2024.03.17 |
GC(Garbage Collection, Garbage Collector) (1) | 2023.10.29 |
에러(Error)와 예외(Exception) (1) | 2023.09.04 |