자바 가상 머신은 자바 프로그램 실행환경을 만들어 주는 소프트웨어입니다. 자바 코드를 컴파일하여 .class 바이트 코드로 만들면 이 코드가 자바 가상 머신 환경에서 실행됩니다.
JVM은 자바 실행 환경 JRE(Java Runtime Environment)에 포함되어 있습니다. 운영체제에 맞는 JRE가 설치되어있으면 자바 프로그램을 실행시킬 수 있습니다.

개발자가 자바로 .java 코드를 작성하고 실행시키면
자바 컴파일러인 javac가 바이트코드인 .class 파일로 컴파일하여 변환합니다.
바이트 코드는 클래스 로더를 통해
JVM이 운영체제로부터 할당받은 메모리 영역인 Runtime Data Area로 적재합니다.
로딩된 .class 바이트 코드를 실행하는 Runtime Module은 Execution Engine입니다.
Executin Engine은 메모리에 적재된 자바 바이트 코드(클래스 파일)를 기계어로 변경해 명령어 단위로 읽어서 실행합니다.
Interpreter 방식은 바이트코드를 한 줄씩 해석, 실행하는 방식으로 느리고,
JIT(Just In Time) 컴파일 방식은 실행하는 시점에 각 OS에 맞는 Native Code로 변환하는 방식으로 속도가 빠르지만 비용이 소모되므로 인터프리터 방식을 사용하다가 일정 기준이 넘어가면 JIT 컴파일 방식으로 명령어를 실행합니다.
JVM의 Runtime Data Area는 5개의 메모리 영역으로 나눌 수 있습니다.

모든 스레드가 공유해서 사용
- 힙 영역 (Heap Area)
- 메서드 영역(Method Area)
스레드(Thread) 마다 하나씩 생성
- 스택 영역(Stack Area)
- PC 레지스터 (PC Register)
- 네이티브 메서드 스택(Native Method Stack
메소드 영역(Method Area)은 JVM(Java Virtual Machine)의 메모리 구조 중 하나로, 모든 스레드가 공유하는 영역입니다. 이 영역은 프로그램이 시작될 때 생성되어 프로그램이 종료될 때까지 유지되는 특징을 가지고 있습니다.
메소드 영역이 생성되는 시점을 살펴보면, 클래스 로더(Class Loader)가 .class 파일을 읽어서 JVM 메모리에 로딩할 때 초기화됩니다. 즉, 우리가 작성한 Java 코드가 컴파일되어 생성된 바이트코드 파일들이 실제로 메모리에 올라가는 순간에 메소드 영역이 구성되는 것입니다.
메소드 영역에는 여러 종류의 정보들이 저장됩니다. 먼저 클래스의 구조적 정보들이 저장되는데, 여기에는 클래스의 이름, 부모 클래스의 이름, 구현하고 있는 인터페이스 목록 등이 포함됩니다. 또한 클래스 내부의 멤버 변수들에 대한 상세 정보도 저장됩니다. 변수의 이름이 무엇인지, 어떤 데이터 타입인지(int, String, 사용자 정의 클래스 등), 그리고 접근 제어자가 public인지 private인지 등의 정보가 모두 기록됩니다.
메소드에 관련된 정보들도 메소드 영역에 저장됩니다. 각 메소드의 이름, 매개변수의 타입과 개수, 반환 타입, 그리고 실제 메소드의 바이트코드 등이 포함됩니다. 이를 통해 프로그램 실행 중에 메소드 호출이 일어날 때 JVM이 어떤 코드를 실행해야 하는지 알 수 있습니다.
상수풀(Constant Pool)도 메소드 영역의 중요한 구성 요소입니다. 상수풀에는 클래스에서 사용되는 모든 문자열 리터럴, 숫자 상수, 클래스나 메소드에 대한 참조 정보 등이 저장됩니다. 예를 들어, 코드에서 "Hello World"라는 문자열을 사용한다면, 이 문자열은 상수풀에 저장되어 재사용됩니다.
스태틱(static) 영역도 메소드 영역에 포함됩니다. static 키워드로 선언된 변수들과 메소드들이 이 영역에 저장되며, 클래스가 로딩되는 시점에 메모리에 할당되어 프로그램이 종료될 때까지 유지됩니다. 이는 객체를 생성하지 않고도 클래스명을 통해 직접 접근할 수 있는 이유이기도 합니다.
final로 선언된 클래스나 변수들의 정보도 메소드 영역에 저장됩니다. final 클래스는 상속이 불가능하고, final 변수는 값 변경이 불가능하다는 특성을 가지고 있으며, 이러한 제약 조건들이 메소드 영역에 기록되어 JVM이 런타임에 이를 검증할 수 있게 됩니다.
다음 내용 정도면 블로그용으로 가장 깔끔한 정리다.
군더더기 없이 핵심만 남김.
자바의 인터페이스는 JVM의 메서드(Method) 영역에 저장된다.
여기서 메서드 영역은 클래스/인터페이스의 메타데이터 정보가 적재되는 공간이다.
예를 들면 이런 정보들이 들어간다:
인터페이스는 객체를 직접 생성하지 않기 때문이다.
즉 정리하면
| 항목 | 저장 장소 |
|---|---|
| 인터페이스 자체 | Method Area |
| 인터페이스를 구현한 객체 | Heap |
인터페이스 = 클래스처럼 Method Area에 저장되는 메타정보다.
인터페이스 자체는 객체가 아니므로 Heap에는 없다.

런타임시 new 키워드로 동적으로 생성된 객체 인스턴스와 배열이 할당되는 영역입니다.
가비지 컬렉션(Garbage Collection)이 이 영역에서 사용하지 않는 객체를 자동으로 정리합니다.
Heap Area는 효율적인 GC를 위해 위와 같이 크게 3가지의 영역으로 나뉘게 됩니다.
Young Generation 영역은 자바 객체가 생성되자마자 저장되고, 생긴지 얼마 안되는 객체가 저장되는 공간입니다. Heap 영역에 객체가 생성되면 최초로 Eden 영역에 할당됩니다. 그리고 이 영역에 데이터가 어느정도 쌓이게 되면 참조정도에 따라 Servivor의 빈 공간으로 이동되거나 회수됩니다.
Young Generation(Eden+Servivor) 영역이 차게 되면 또 참조정도에 따라 Old영역으로 이동 되게 되거나 회수됩니다. 이렇게 Young Generation과 Tenured Generation 에서의 GC를 Minor GC 라고 합니다. Old영역에 할당된 메모리가 허용치를 넘게 되면, Old 영역에 있는 모든 객체들을 검사하여 참조되지 않는 객체들을 한꺼번에 삭제하는 GC가 실행됩니다. 시간이 오래 걸리는 작업이고 이 때 GC를 실행하는 쓰레드를 제외한 모든 스레드는 작업을 멈추게 됩니다. 이를 'Stop-the-World' 라 합니다. 메모리가 클수록 Stop-the-World 시간도 길어집니다. 그리고 이렇게 'Stop-the-World'가 발생하고 Old영역의 메모리를 회수하는 GC를 Major GC라고 합니다.
자바 스택 영역(Java Virtual Machine Stacks)은 JVM 메모리 구조에서 각 스레드마다 독립적으로 할당되는 메모리 공간입니다. 이 영역의 가장 중요한 특징은 스레드별로 별도로 생성된다는 점입니다. 즉, 멀티스레드 환경에서 각 스레드가 시작될 때마다 해당 스레드만을 위한 전용 스택 영역이 만들어지며, 다른 스레드는 이 영역에 접근할 수 없습니다. 예를 들어, 프로그램에서 10개의 스레드가 동시에 실행된다면, JVM은 각각의 스레드를 위해 총 10개의 독립적인 스택 영역을 생성합니다. 각 스택 영역은 완전히 분리되어 있어서 한 스레드의 작업이 다른 스레드의 스택에 영향을 주지 않습니다. 이러한 격리된 구조 덕분에 스레드 간의 데이터 충돌이나 간섭 없이 각각 독립적으로 작업을 수행할 수 있습니다.
스택 영역에는 여러 종류의 데이터가 저장됩니다. 메소드 내부에서 선언된 지역 변수들이 이곳에 생성되며, 이러한 지역 변수들은 해당 메소드의 실행이 끝나면 자동으로 메모리에서 제거됩니다. 또한 메소드 호출 시 전달되는 파라미터(매개변수)들도 스택 영역에 저장되어 메소드 내부에서 사용됩니다. 메소드가 값을 반환할 때의 리턴 값과 연산 과정에서 필요한 임시 값들도 이 영역에서 처리됩니다.
스택 영역의 동작 방식을 이해하기 위해서는 스택 프레임(Stack Frame)이라는 개념을 알아야 합니다. 각 스레드의 스택 영역 내에서 메소드가 호출될 때마다 새로운 스택 프레임이 생성됩니다. 만약 10개의 스레드가 있다면 각 스레드마다 자신만의 스택 영역이 있고, 각 스택 영역에서는 메소드 호출에 따라 독립적으로 스택 프레임들이 생성되고 제거됩니다. 즉, 스레드 A에서 메소드를 호출하면 스레드 A의 스택에만 스택 프레임이 추가되고, 스레드 B에서 메소드를 호출하면 스레드 B의 스택에만 스택 프레임이 추가되는 방식입니다.
스택 프레임은 해당 메소드의 실행에 필요한 모든 정보를 담고 있는 독립적인 공간으로, 메소드의 지역 변수, 매개변수, 메소드 호출과 관련된 정보, 그리고 메소드가 끝난 후 돌아갈 주소(복귀 주소) 등이 저장됩니다. 스택 프레임은 메소드가 호출되는 순서대로 차곡차곡 쌓이게 되며, 가장 최근에 호출된 메소드의 스택 프레임이 맨 위에 위치하게 됩니다.
스택의 특성상 후입선출(LIFO: Last In First Out) 방식으로 동작합니다. 즉, 가장 나중에 호출된 메소드가 가장 먼저 종료되고, 해당 스택 프레임이 제거됩니다. 메소드의 실행이 완료되면 그 메소드에 해당하는 스택 프레임이 스택에서 제거되면서, 그 안에 저장되어 있던 지역 변수와 매개변수들도 함께 메모리에서 사라집니다. 이후 프로그램의 실행 흐름은 이전 메소드로 돌아가게 됩니다.
실제 자바 프로그램을 실행하는 과정을 예로 들어보면, 프로그램이 시작되면 가장 먼저 main 스레드가 생성되고, main 메소드가 호출됩니다. 이때 main 스레드의 스택 영역에 main 메소드를 위한 스택 프레임이 생성됩니다. main 메소드 내부에서 다른 메소드를 호출하면, 새로운 스택 프레임이 생성되어 기존 main 스택 프레임 위에 쌓입니다. 만약 프로그램에서 추가로 9개의 새로운 스레드를 생성한다면, 총 10개의 스레드가 존재하게 되고, 각각은 자신만의 독립적인 스택 영역을 가지게 됩니다. 각 스레드에서 메소드 호출이 발생할 때마다 해당 스레드의 스택에만 스택 프레임이 추가되며, 다른 스레드의 스택에는 전혀 영향을 주지 않습니다.
메소드의 실행이 끝나면 역순으로 스택 프레임이 제거됩니다. 가장 나중에 호출된 메소드가 먼저 종료되고 해당 스택 프레임이 제거되며, 그 다음으로 호출된 메소드가 종료되는 식으로 진행됩니다. 각 스레드의 모든 작업이 완료되면 해당 스레드의 스택 영역 전체가 메모리에서 해제됩니다.
Thread가 생성될 때마다 생성되는 영역으로 프로그램 카운터, 즉 현재 스레드가 실행되는 부분의 명령어의 주소를 저장하는 영역입니다. 스레드가 다음에 실행할 명령어를 가리키는 역할을 합니다.
PC Register는 Stack-Base로 동작합니다. 스택 프레임 내에서 메소드 호출이 발생할 때마다 새로운 PC Register 값이 생성되어 현재 수행 중인 명령어의 주소를 가리킵니다.
각 스레드마다 별도의 PC Register가 존재하므로 다중 스레드 환경에서 각각의 스레드가 독립적으로 메소드를 실행하고 스레드 간 전환 시에도 정확한 실행 흐름을 유지할 수 있게 해줍니다.
자바 이외의 언어(C, C++, 어셈블리 등)로 작성된 네이티브 코드를 실행할 때 사용되는 메모리 영역으로 일반적인 C 스택을 사용합니다.
보통 C/C++ 등의 코드를 수행하기 위한 스택을 말하며 (JNI) 자바 컴파일러에 의해 변환된 자바 바이트 코드를 읽고 해석하는 역할을 하는 것이 자바 인터프리터(interpreter)입니다.
┌──────────────────────────────┐
│ Method Area ← 클래스 관련 정보 저장 영역
│ ─────────────────────────────
│ • 클래스 메타데이터
│ • static 변수
│ • 메서드 코드(바이트코드)
│ • 메서드 테이블
└──────────────────────────────┘
┌──────────────────────────────┐
│ Heap ← new 로 생성한 객체 저장
│ ─────────────────────────────
│ • 인스턴스 변수
│ • 배열, 객체 데이터
│ • GC(가비지 컬렉션) 대상
└──────────────────────────────┘
┌──────────────────────────────┐
│ Stack ← 메서드 호출 시 Frame 생성
│ ─────────────────────────────
│ [Frame]
│ • 지역 변수(매개변수 포함)
│ • 연산 중간 결과(Operand)
│ • 메서드 리턴 주소
│
│ [Frame] → push (메서드 호출 시)
│ [Frame] → pop (메서드 종료 시)
└──────────────────────────────┘
가비지는 '정리되지 않은 메모리', '유효하지 않은 메모리 주소'를 말합니다. 주소를 잃어버려서 사용할 수 없는 메모리가 '정리되지 않은 메모리'입니다. 프로그래밍 언어에서는 Danling Object, 자바에서는 Garbage라고 부릅니다.
C++와 같은 다른 언어에서는 사용하지 않을 객체의 메모리를 직접 해제해주어야 하지만 자바는 GC가 잡아주니 개발자 입장에서는 편리하지만 모든 메모리 누수를 잡아주는 것은 아님으로 메모리 누수에 대한 경계를 늦추어서는 안됩니다.
가비지 컬렉션은 영어로 Garbeage Collection으로 줄여서 GC라고도 부릅니다. 가비지 컬렉션은 자바의 메모리 관리 방법 중의 하나로 JVM의 Heap 영역에서 동적으로 할당했던 메모리 영역 중 필요 없게 된 메모리 영역을 주기적으로 삭제하는 프로세스를 말합니다. C나 C++에서는 이러한 가비지 컬렉션이 없어 프로그래머가 수동으로 메모리 할당과 해제를 일일이 해줘야 하는 반면 Java는 JVM에 탑재되어 있는 가비지 컬렉터가 메모리 관리를 대행해주기 때문에 개발자 입장에서 메모리 관리, 메모리 누수(Memory Leak) 문제에서 대해 완벽하게 관리하지 않아도 되어 오롯이 개발에만 집중할 수 있다는 장점이 있습니다.
단점으로는 개발자가 메모리가 언제 해제되는지 정확하게 알 수 없고,
가비지 컬렉션(GC)이 동작하는 동안에는 다른 동작을 멈추기 때문에 오버헤드가 발생합니다.
GC가 너무 자주 실행되면 소프트웨어 성능 하락의 문제가 되기도 합니다.
가비지 컬렉션은 힙영역에서 이루어집니다.
객체 생성: 프로그램 실행 중에 객체들이 동적으로 생성됩니다. Heap 메모리에 객체들이 할당됩니다.
참조 체크: JVM은 객체들 간의 참조 관계를 추적합니다. 객체는 다른 객체에 의해 참조될 수 있습니다. 각 객체가 얼마나 많은 다른 객체에 의해 참조되는지를 확인합니다.
Reachability 분석: 루트(root) 객체들로부터 그래프 순회를 통해 참조 체인을 따라가며 각 객체가 도달 가능한지 확인합니다. 도달 가능한 객체들은 "존재하는" 객체로 간주됩니다.
Unreachable 객체 식별: Reachability 분석을 통해 도달 가능하지 않은 객체들이 식별됩니다. 이러한 객체들은 더 이상 사용되지 않는 객체로 간주됩니다. 이 단계에서 가비지 컬렉션의 대상이 되는 객체가 결정됩니다.
가비지 컬렉션 실행: 불필요한 객체들이 식별되면 JVM은 이러한 객체들을 해제하여 메모리를 반환합니다. 이 과정은 가비지 컬렉터(Garbage Collector)가 실행되어 수행됩니다.
메모리 반환: 가비지 컬렉터가 메모리를 반환하는 과정에서 객체가 차지하던 메모리 공간이 해제되고, 분산된 객체들을 Heap의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 압축합니다. 사용 가능한 공간이 늘어납니다.
안녕하세요! 부스트캠프에서 보고 인상깊어서 댓글 남겨요! 너무 고생하셨어요! 저도 컴파일러 공부하면서 눈이 많이 열렸는데, 나중에 꼭 포스트로 뵙고 싶네요. 제 벨로그도 놀러오세요!