본문 바로가기

Java

JVM 구조

 

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가지 블럭으로 나뉜다.

  1. 메소드 영역(Method Area) – 모든 스레드가 공유하는 영역
    - 런타임 상수 풀(Runtime Constant Pool), 필드 데이터, 메소드 데이터가 저장되는 공간
    - 보다 간단히 말해 클래스의 정보(클래스 이름, 부모 클래스 이름, 인터페이스, 메소드, 변수, 접근 제어자)가 저장되는 공간이라고 할 수 있다.

    * 런타임 상수 풀(Runtime Constant Pool)
    클래스와 인터페이스의 상수 데이터를 저장하는 동적 구조. 리터럴 상수나 심볼릭 참조(클래스와 인터페이스 이름, 필드 이름, 메소드 이름 ) 정보가 저장된다.

    * 심볼릭 참조(Symbolic Reference)
    특정 객체를 참조할 때 Memory Address를 직접 참조하는 게 아니라 객체의 이름으로 참조하는 방식을 의미한다.

  2. 힙 영역(Heap) – 모든 스레드가 공유하는 영역
    - 프로그램을 실행하면서 생성된 모든 객체가 저장되는 공간.
    - 인스턴스화 된 모든 클래스와 배열이 저장되는 공간이다.
    - 힙에 할당된 메모리 회수는 무조건 가비지 컬렉터(Garbage Collector)에 의해서만 회수가 가능하다.

  3. 스택 영역(Java Stack) – 각 스레드 별로 존재하는 영역
    - 메소드를 실행하기 위한 정보들이 저장되는 공간이다 
    - 메소드가 호출될 때마다 스택 프레임이 스택에 push 되고 메소드 실행이 완료되면 pop된다.
    - 우리가 흔히 재귀 함수를 잘못 사용하여 무한정 메소드가 재 생성될 때 Stack Overflow 에러가 발생하는 이유가 이러한 스택 영역의 특성 때문이다.

    * 스택 프레임(Stack Frame)
    메소드 호출과 실행에 필요한 정보를 담고 있는 구조체이다. 스택 프레임 안에 들어있는 정보는 다음과 같다.

    1) 로컬 변수 배열(Local Variable Array) : 메소드의 파라미터와 로컬 변수가 저장되는 배열. 파라미터와 변수는 인덱스를 통해 접근할 있으며 배열의 크기는 컴파일 타임에 결정된다.
    2) 연산 스택(Operand Stack) : 메소드 실행 도중 발생하는 연산의 중간 결과가 저장된다. 산술 연산이나 메소드 호출의 결과 등이 스택에 저장된다.
    3) 프레임 데이터(Frame Data) : 메소드 호출과 관련된 추가 정보가 저장된다. 동적 링크 정보, 메소드를 호출한 위치의 반환 주소 등이 여기에 포함된다.

  4. PC 레지스터 영역(PC Registers) – 각 스레드 별로 존재하는 영역
    - 스레드가 현재 실행하고 있는 부분의 주소를 저장한다. OS는 PC(Program Counter) Register를 참고하여 CPU 스케줄링 시 해당 스레드가 다음에 어떤 명령어를 수행해야 하는지 알 수 있다.

  5. 네이티브 메소드 영역(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++로 작성 된 라이브러리

[JVM 실행 과정]

 


 

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 네이티브 메소드 호출이 없으므로 이 영역은 사용되지 않는다.


출처 : 
https://coding-factory.tistory.com/827
https://itminco.tistory.com/22
https://lazymankook.tistory.com/79
https://www.youtube.com/watch?v=GU254H0N93Y
https://www.youtube.com/watch?v=UzaGOXKVhwU
https://www.youtube.com/watch?v=vZRmCbl871I&t=124s

'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