본문 바로가기
Language/Java

[JAVA] JVM의 내부 구조 완벽 해부 : 메모리 관리의 핵심 원리

by Papa Martino V 2026. 1. 25.
728x90

JVM
JVM

 

자바 프로그래머라면 코드가 '어디서' 실행되는지 이해하는 것이 매우 중요합니다. 우리가 작성한 자바 코드는 운영체제 위에서 직접 실행되는 것이 아니라, JVM(Java Virtual Machine, 자바 가상 머신)이라는 중개자를 통해 실행됩니다. 이 구조 덕분에 자바는 'Write Once, Run Anywhere'라는 철학을 실현할 수 있었습니다. 이번 글에서는 JVM의 전체 구조와 핵심 메모리 영역인 Runtime Data Areas의 각 역할을 깊이 있게 살펴보겠습니다.

1. JVM의 전체 아키텍처 구조

JVM은 단순히 코드를 해석하는 엔진을 넘어, 메모리 관리와 실행 최적화를 담당하는 복합적인 시스템입니다. 전체 구조는 크게 세 가지 서브시스템으로 나뉩니다.

  • Class Loader System: 컴파일된 .class 파일을 읽어들여 런타임 데이터 영역에 배치합니다.
  • Runtime Data Areas: JVM이 프로그램을 수행하기 위해 운영체제로부터 할당받은 메모리 공간입니다.
  • Execution Engine: 메모리에 배치된 바이트코드를 실행하며, 이 과정에서 JIT 컴파일러와 Garbage Collector가 작동합니다.

2. Runtime Data Areas: JVM 메모리의 5대 영역

가장 핵심적인 부분은 데이터가 저장되는 Runtime Data Areas입니다. 각 영역은 데이터의 성격과 생명주기에 따라 다르게 관리됩니다.

① Method Area (메서드 영역)

모든 스레드가 공유하는 영역입니다. JVM이 시작될 때 생성되며, 클래스 수준의 정보(클래스 이름, 부모 클래스 이름, 메서드 데이터, 필드 데이터, static 변수 등)를 저장합니다. 런타임 상수 풀(Runtime Constant Pool)도 이 영역에 포함되어 상수들의 참조를 관리합니다.

② Heap (힙 영역)

모든 스레드가 공유하며, new 키워드로 생성된 객체(인스턴스)와 배열이 저장되는 곳입니다. JVM 메모리 관리의 핵심인 Garbage Collection(GC)의 주 대상이 되는 영역입니다. 참조되지 않는 객체는 GC에 의해 자동으로 제거됩니다.

③ Stack (스택 영역)

각 스레드마다 독립적으로 생성되는 영역입니다. 메서드가 호출될 때마다 'Stack Frame'이라는 블록이 추가되고, 메서드가 종료되면 제거됩니다. 지역 변수, 매개 변수, 리턴 값, 연산 중간 결과 등이 저장됩니다.

④ PC Register (PC 레지스터)

스레드마다 생성되며, 현재 실행 중인 JVM 명령의 주소를 저장합니다. CPU 내의 레지스터와 유사한 역할을 수행하며, 다음에 실행할 명령의 위치를 가리키는 포인터 역할을 합니다.

⑤ Native Method Stack (네이티브 메서드 스택)

자바 외의 언어(C, C++ 등)로 작성된 네이티브 코드를 실행하기 위한 메모리 영역입니다. JNI(Java Native Interface)를 통해 호출되는 메서드들이 이 스택을 사용합니다.

3. JVM 메모리 영역 비교 분석

각 영역의 차이점을 한눈에 파악할 수 있도록 표로 정리했습니다.

구분 공유 범위 저장 데이터 생성 시점 관리 주체
Method Area 모든 스레드 클래스 정보, static 변수, 상수 JVM 시작 시 JVM
Heap 모든 스레드 객체 인스턴스, 배열 JVM 시작 시 Garbage Collector
Stack 스레드별 독립 지역 변수, 매개 변수, 프레임 정보 스레드 시작 시 OS/JVM (자동 제거)
PC Register 스레드별 독립 현재 실행 중인 명령 주소 스레드 시작 시 JVM
Native Stack 스레드별 독립 네이티브 언어(C/C++) 메서드 정보 스레드 시작 시 JVM

4. 실전 예제를 통한 메모리 흐름 이해 (Sample Example)

다음 코드가 실행될 때 JVM 메모리 내부에서 어떤 일이 일어나는지 살펴보겠습니다.


public class MemoryExample {
    public static void main(String[] args) {
        int limit = 10; // Stack 영역에 저장
        Person p1 = new Person("Java"); // p1 참조변수는 Stack, Person 객체는 Heap에 저장
        p1.sayHello();
    }
}

class Person {
    private String name;
    public Person(String name) { this.name = name; }
    public void sayHello() { System.out.println("Hello, " + name); }
}
  1. Method Area: MemoryExample 클래스와 Person 클래스의 구조 정보, 메서드 코드가 로드됩니다.
  2. Stack: main 메서드가 실행되면서 스택 프레임이 생성됩니다. limit 변수 값 10과 p1이라는 참조 변수가 스택에 놓입니다.
  3. Heap: new Person("Java")를 통해 실제 데이터 객체가 힙 메모리에 생성되고, 그 주소값이 스택의 p1에 전달됩니다.
  4. Execution: sayHello() 호출 시 새로운 스택 프레임이 생성되며 작업이 수행됩니다.

5. 결론: 왜 JVM 구조를 알아야 하는가?

효율적인 자바 애플리케이션 작성을 위해서는 메모리 구조 이해가 필수적입니다. 무분별한 객체 생성은 Heap 영역의 부하를 주어 빈번한 GC(Stop-the-world)를 유발하고, 너무 큰 재귀 호출은 Stack Overflow를 발생시킵니다. 각 영역의 특성을 이해하고 코딩한다면 훨씬 더 안정적이고 고성능의 소프트웨어를 개발할 수 있습니다.


출처 및 참고문헌:

  • Oracle, "The Java Virtual Machine Specification, Java SE 21 Edition"
  • James Gosling, "The Java Programming Language", Addison-Wesley
  • Baeldung, "JVM Memory Management" Technical Guide
728x90