JVM 구조

jeong_hyeok·2025년 8월 12일

Class Loader Subsystem

  • Subsystem: 시스템을 구성하는 요소의 하나로, 그 자체로도 시스템을 형성하고 있는 것

주로 3가지 동작을 담당한다.

  • Loading
  • Linking
  • Initialization

Loading

클래스 로더가 .class 파일을 읽고 바이너리 데이터를 생성하여 메서드 영역에 저장한다. 메서드 영역에는 다음과 같은 정보들이 .class 파일마다 저장된다.

  • 클래스와 바로 위 조상 클래스의 완전한 이름(full qualified name, 클래스가 속한 패키지명을 모두 포함한 이름)
  • .class 파일이 클래스인지 / 인터페이스인지 / enum인지
  • 제어자(modifier), 변수, 메서드 정보

.class 파일을 로드한 후, JVM은 이 파일을 표현하기 위해 힙 메모리에 Class 타입의 객체를 생성한다.

  • 이 객체는 java.lang 패키지에 미리 정의되어 있는 Class 타입이다.

  • Class 객체는 프로그래머가 클래스 수준의 정보(예: 클래스 이름, 부모 이름, 메서드와 변수 정보 등)를 얻는 데 사용할 수 있다.

  • 이 객체의 참조를 얻기 위해서는 Object 클래스의 getClass() 메서드를 사용할 수 있다.

Example:

// A Java program to demonstrate working
// of a Class type object created by JVM
// to represent .class file in memory
import java.lang.reflect.Field;
import java.lang.reflect.Method;

// Java code to demonstrate use
// of Class object created by JVM
public class Geeks
{
    public static void main(String[] args)
    {
        Student s1 = new Student();

        // Getting hold of Class
        // object created by JVM.
        Class c1 = s1.getClass();

        // Printing type of object using c1.
        System.out.println(c1.getName());

        // getting all methods in an array
        Method m[] = c1.getDeclaredMethods();
        for (Method method : m)
            System.out.println(method.getName());

        // getting all fields in an array
        Field f[] = c1.getDeclaredFields();
        for (Field field : f)
            System.out.println(field.getName());
    }
}

// A sample class whose information
// is fetched above using its Class object.
class Student {
    private String name;
    private int roll_No;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getRoll_no() { return roll_No; }
    public void setRoll_no(int roll_no)
    {
        this.roll_No = roll_no;
    }
}

Output

Student
getName
setName
getRoll_no
setRoll_no
name
roll_No

참고로, 로드된 모든 .class 파일에, Class 객체는 하나씩만 생성된다.

Student s2 = new Student();
// c2 will point to same object where 
// c1 is pointing
Class c2 = s2.getClass();
System.out.println(c1==c2); // true

Linking

검증(verification), 준비(preparation), 그리고 (선택적으로) 해석(resolution)을 수행한다.

  • 검증(verification): .class 파일의 정확성을 보장한다. (올바르게 형식화되어 있는지, 유효한 컴파일러에 의해 생성되었는지)
  • 준비(preparation): JVM이 클래스의 static 변수에 메모리를 할당하고, 그 메모리를 기본값으로 초기화한다.
  • 해석(Resolution): 타입의 심볼릭 참조(symbolic reference, 예: "java/lang/String", "java/util/List" 등의 이름)를 직접 참조(direct reference, 객체의 실제 메모리 주소를 참조)로 교체하는 과정이다. 이는 메서드 영역(Method Area)에서 참조된 엔터티를 찾아서 수행된다.

Initialization

이 단계에서는 모든 static 변수에 코드와 static 블록(있을 경우)에 정의된 값이 할당된다.

이는 클래스 내에서는 위에서 아래로, 클래스 계층 구조에서는 부모에서 자식 순서로 실행된다.

JVM Memory Areas

Java 런타임 데이터 영역(runtime data area)의 일부는 JVM이 생성하고, 일부는 프로그램에서 사용되는 쓰레드가 생성한다.

  • JVM이 만든 메모리 영역은 JVM이 종료될 때에만 파괴된다.
  • 쓰레드가 만든 메모리 영역은 쓰레드가 생성될 때 만들어지고, 쓰레드가 종료되면 파괴된다.

JVM 메모리 구조에는 다음 영역들이 포함된다:

  • Heap Area
  • Method Area
  • JVM Stacks
  • Native Method Stacks
  • Program Counter (PC) Registers

Heap Area

  • JVM이 시작할 때 만들어진다. JVM 당 하나의 힙 영역만 만들어지고, 공유되는 자원이다.
  • 객체와 배열들이 저장된다.
  • 실행 중인 JVM 프로세스에는 단 하나의 힙만 존재한다.
  • 공유되는 런타임 데이터 영역이다.
  • 사용자가 heap 크기를 조정할 수 있다.
  • new 키워드를 사용하면 객체는 힙에 할당되고, 그 객체의 참조값은 스택에 저장된다.

Scanner sc = new Scanner(System.in)
여기서 Scanner 객체는 힙에, 참조인 sc는 스택에 저장된다.

Method Area

  • JVM이 시작할 때 만들어진다. JVM 당 하나의 메서드 영역만 만들어지고, 공유되는 자원이다.
  • 메서드 영역은 논리적으로 힙의 일부이지만, HotSpot 같은 많은 JVM들은 힙 외부의 분리된 공간인 Metaspace라는 곳에 메서드 영역을 저장한다.
  • 클래스 구조, 메서드 바이트코드, static 변수, 상수 풀(constant pool), 인터페이스 등 클래스 수준(class-level) 정보를 저장하는 데 사용된다.
  • 시스템 구성에 따라 고정된 크기를 가질 수도 있고, 동적인 크기를 가질 수도 있다.
  • 메서드 영역에 대한 가비지 컬렉션은 보장되지 않는다. JVM 구현 방식에 따라 다르다.

JVM Stacks

  • 쓰레드가 생성될 때 만들어진다.
  • 지역 변수, 메서드 인자, return 주소와 같은 메서드 실행 데이터를 저장하는 데 사용된다.
  • 각 쓰레드는 자신만의 스택을 가진다.
  • 스택의 크기는 고정일 수도 있고, 동적일 수도 있다. 스택이 생성될 때 설정할 수 있다.
  • 스택 메모리는 연속적일 필요 없다.
  • 메서드가 실행을 완료하면, 연관된 스택 프레임은 자동으로 제거된다.

Native Method Stacks

  • 쓰레드가 생성될 때 할당된다.
  • C 스택이라고도 한다.
  • 자바 언어로 작성되어 있지 않다.
  • 고정된 크기 혹은 동적인 크기 모두 가질 수 있다.
  • 자바 코드와 상호작용하는 네이티브 메서드의 실행을 처리한다.

Program Counter(PC) Registers

  • 각 JVM 쓰레드는 PC 레지스터를 가진다.
  • non-native 메서드에서는 현재 JVM 명령어의 주소를 저장한다.
  • native 메서드에서는 PC 값이 undefined이다.

Execution Engine

실행 엔진은 .class(바이트코드)를 실행한다. 바이트코드를 한 줄씩 읽고, 다양한 메모리 영역에 있는 데이터와 정보를 사용하여 명령어들을 실행한다.

실행 엔진은 세 부분으로 나뉜다.

  • Interpreter: 바이트코드를 한 줄씩 해석해서 실행한다. 단점은, 한 메서드가 여러 번 호출되면 매번 해석을 해야 한다는 것이다.

  • Just-In-Time Compiler(JIT): 인터프리터의 효율을 높이기 위해 사용된다. 자주 실행되는 코드 블록을 바이트코드를 컴파일하여 네이티브 코드(특정 하드웨어나 운영 체제에서 직접 실행될 수 있는 코드)로 변환한다. 인터프리터가 반복된 메서드 호출을 마주하면 JIT가 해당 부분에 대한 네이티브 코드를 제공하여 중복 해석을 방지한다.

  • Garbage Collector: 참조되지 않은 객체들을 제거한다.

Java Native Interface (JNI)

Native Method Library와 상호작용하여 실행에 필요한 네이티브 라이브러리(C, C++)를 제공하는 인터페이스

JVM이 특정 하드웨어에 종속적인 C/C++ 라이브러리를 호출하거나 해당 라이브러리에 호출받을 수 있도록 해준다.

Native Method Libraries

네이티브 메서드들을 실행하는 데 필요한 네이티브 라이브러리들의 모음

0개의 댓글