
자바 애플리케이션의 성능을 분석하거나 메모리 문제를 해결하려면 JVM(Java Virtual Machine)의 메모리 구조를 정확히 이해하는 것이 무엇보다 중요합니다.
오늘은 아래 내용을 같이 다뤄볼게요
- JVM 메모리가 어떤 구조로 이루어져 있는지
- 메서드 영역, 힙, 스택이 각각 어떤 역할을 하는지
- 객체와 클래스 정보가 어떻게 저장되는지
- 메서드 호출 시 스택 프레임이 어떤 방식으로 동작하는지
- 실제 코드 예제를 통해 살펴보는 JVM 메모리의 상호작용
이 글을 읽으면 자바 프로그램이 메모리를 어떻게 사용하며 실행되는지 전체 흐름을 자연스럽게 이해할 수 있을 거예요.
JVM 메모리 구조가 중요한 이유
JVM 메모리를 이해하면 다음과 같은 문제를 더 정확하게 진단하고 최적화할 수 있습니다.
- 메모리 누수나
OutOfMemoryError의 원인을 파악할 수 있습니다. - GC(Garbage Collector)가 어떻게 동작하는지 이해하는 데 필수적입니다.
- 힙 크기나 스레드 스택 크기처럼 성능에 영향을 주는 튜닝 포인트를 올바르게 판단할 수 있습니다.
- 객체의 생성부터 소멸까지 이어지는 생명주기를 파악해 더 효율적인 구조로 최적화할 수 있습니다.
JVM 메모리 구조

JVM 메모리는 크게 다음 세 가지 핵심 영역으로 구성됩니다.
- 메서드(Method) 영역
- 힙(Heap)
- 스택(Stack)
아래에서 각 영역이 어떤 역할을 하는지 자세히 살펴보겠습니다.
메서드 영역(Method Area)
메서드 영역은 클래스 수준의 정보를 저장하는 공용 메모리 공간입니다. 주요 특징은 다음과 같습니다.
- 클래스 로더가 로드한 클래스·인터페이스의 메타데이터 저장
static변수 저장- 상수(Constant Pool) 저장
- 모든 스레드가 공유
한 번 로드된 클래스 정보는 프로그램 전체에서 사용되며, 인스턴스가 여러 개 생성되더라도 메서드 코드는 중복으로 만들어지지 않습니다.
힙 영역(Heap)
힙은 JVM에서 가장 큰 메모리 영역으로, 모든 객체 인스턴스와 배열이 저장되는 공간입니다.
new키워드로 생성된 객체 저장- GC(Garbage Collector)가 객체의 생명주기를 관리
- 모든 스레드가 공유
힙을 어떻게 설정하고 관리하느냐에 따라 애플리케이션의 성능과 안정성이 크게 달라지기 때문에 특히 중요한 영역입니다.
스택 영역(Stack)
스택 영역은 스레드마다 독립적으로 생성되는 메모리 공간으로, 메서드 호출 시 만들어지는 스택 프레임(Stack Frame)을 저장합니다.
스택 프레임에는 다음 정보가 포함됩니다.
- 지역 변수
- 매개변수
- 메서드 실행 상태 정보
메서드가 호출되면 스택 프레임이 차곡차곡 쌓이고, 실행이 끝나면 제거되는 LIFO(Last In, First Out) 구조를 따릅니다.
메서드 영역과 힙 영역의 관계
여기서 주로 혼동되는 개념이 바로 메서드 영역과 힙 영역의 관계에 대한 개념입니다.
예시를 들어보겠습니다.
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void sayHello() {
System.out.println("안녕 내 이름은 " + name + "이고, 나이는 " + age + "살이야");
}
}
public class Main {
public static void main(String[] args) {
Person person1 = new Person("chulsoo", 30);
Person person2 = new Person("yeonghee", 25);
person1.sayHello();
person2.sayHello();
}
}
위 예시에서, Person 클래스는 해당 객체의 속성을 나타내는 변수 name과 age를 가지고 있고, sayHello라는 메서드를 포함하고 있ㅆ브니다.
그리고 main 메서드에서 Person의 인스턴스 두개를 생성하고 각각에 대해 sayHello를 호출합니다.

그 경우, 힙 영역에 Person의 인스턴스인 person1과 person2가 생성되는데, 이때 해당 인스턴스에 메서드까지 가지고 있지 않고,
전체적인 Person 클래스의 내용을 메타데이터의 형태로 메서드 영역에 저장후, 이 정보를 참조해서 메서드를 실행하게 됩니다.
즉, Person 클래스가 처음 JVM에 로드될 때, sayHello의 메서드 코드는 메서드 영역에 한번만 배치되며 모든 Person의 인스턴스가 해당 메서드를 공유하여 사용할 수 있습니다.
스택 영역의 동작
public class StackExample {
public static void main(String[] args) {
System.out.println("Start main");
firstMethod();
System.out.println("End main");
}
public static void firstMethod() {
System.out.println("Inside firstMethod");
secondMethod();
System.out.println("End of firstMethod");
}
public static void secondMethod() {
System.out.println("Inside secondMethod");
}
}
위 코드를 실행시키는 자바 어플리케이션이 있다고 가정해봅시다.
이 코드는 main 메서드에서 시작해서 firstMethod를 호출하고, 그 다음 firstMethod 내에서 secondMethod를 호출합니다.
이 코드가 실행될 때 스택 영역의 동작을 살펴봅시다.

- 프로그램이 실행되고 main 메서드를 위한 첫번째 스택 프레임이 생성됩니다. 이 프레임은 main메서드의 지역 변수와 호출 정보를 담고 있습니다.
- 이후 main 메서드 내에서 firstMethod가 호출되어 새로운 스택 프레임이 main 프레임 위에 추가됩니다. 이 프레임은 firstMethod의 지역변수와 호출 정보를 담고 있습니다.
- firstMethod내에서 secondMethod도 호출되면서 해당 스택프레임이 firstMethod위에 생성됩니다. 마찬가지로 해당 메서드의 지역변수와 호출 정보를 담고 있습니다.
- 이후 secondMethod가 종료되면 스택 프레임에서 해당 스택이 제거됩니다.
- 그 다음 firstMethod도 종료되어 해당 스택프레임이 제거됩니다.
- 마지막으로 main 메서드 까지 실행이 종료되면 마지막 남은 스택 프레임이 제거되고 프로그램이 종료됩니다.
이러한 방식으로 메서드 호출 시 해당 실행 컨텍스트를 저장하고 있어서, 메서드 호출과 실행흐름을 관리하기 용이한 구조를 제공합니다.
스택 영역과 힙 영역의 동작
class Book {
String title;
int page;
public Book(String title, int page) {
this.title = title;
this.page = page;
}
public void printInfo() {
System.out.println(title + "은 " + page + "페이지로 구성되어있다");
}
}
public class Library {
public static void main(String[] args) {
Book book1 = new Book("Do it 자바 프로그래밍", 350);
Book book2 = new Book("헤드퍼스트 자바", 500);
book1.printInfo();
book2.printInfo();
}
}
위 코드를 실행시키는 자바 어플리케이션이 있다고 가정했을 때,
해당 코드가 실행되면서 힙 영역과 스택 영역이 어떻게 상호작용할까요?
- main 메서드가 호출되면서 이에 대한 스택 프레임이 스택 영역에 생성됩니다.
- Book 객체들이 힙 영역에 생성됩니다.
- book1.printInfo() / book2.printInfo() 메서드 호출 시 각 메서드에 대한 스택 프레임이 스택 영역에 생성됩니다.
이렇게 스택 영역과 힙 영역, 메서드 영역에 필요한 정보들이 저장되어 호출되는 것이죠!
'Language > Java ☕️' 카테고리의 다른 글
| Java static 키워드 완전 가이드 (0) | 2024.05.22 |
|---|---|
| 자바의 가비지 컬렉션(GC) 한 번에 이해하기 (0) | 2024.02.02 |
| String vs StringBuffer vs StringBuilder — 자바 문자열 클래스의 차이 완전 정리 (0) | 2024.02.02 |
| Double과 Float를 사용하면 안되는 이유 (0) | 2024.02.01 |
| JDK, JRE, JVM의 차이와 자바 실행 구조 이해하기 (0) | 2024.02.01 |
안녕하세요, 저는 주니어 개발자 박석희 입니다. 언제든 하단 연락처로 연락주세요 😆