프로그래밍 언어/java

[Java] JVM 메모리 구조 파헤치기

하루이2222 2024. 7. 28. 17:56

Java 개발자 필수 기초: JVM 메모리 구조 파헤치기 (스택, 힙, 메소드 영역)

우리가 작성한 Java 코드는 어떻게 컴퓨터 메모리 위에서 살아 움직일까요? 왜 메소드가 끝나면 그 안의 변수는 사라지는데, new로 만든 객체는 계속 남아있을까요? 이 모든 질문의 해답은 JVM(자바 가상 머신)의 메모리 구조에 있습니다.

JVM이 메모리를 어떻게 체계적으로 나누어 사용하는지 이해하는 것은 메모리 누수를 방지하고, 성능을 최적화하며, 버그를 잡는 데 매우 중요한 기초 체력입니다.

오늘은 JVM 메모리의 핵심 영역인 **스택(Stack), 힙(Heap), 메소드 영역(Method Area)**을 알아보고, 실제 코드가 각 영역에 어떻게 할당되는지 그 과정을 따라가 보겠습니다.

JVM 메모리의 3가지 핵심 영역

JVM은 메모리를 크게 세 가지 영역으로 구분하여 관리합니다.

  1. 스택 영역 (Stack Area) : 메소드의 작업실
    • 역할: 메소드 호출 시 필요한 지역 변수, 매개변수, 그리고 연산의 중간 결과 등이 저장되는 임시 작업 공간입니다. 스레드마다 별도의 스택이 할당됩니다.
    • 특징: 메소드가 호출될 때 스택 프레임(Stack Frame)이 생성되고, 메소드 실행이 끝나면 해당 프레임이 제거됩니다(LIFO: Last-In, First-Out). 개발자가 직접 초기화해야 하며, 그렇지 않으면 컴파일 에러가 발생합니다.
  2. 힙 영역 (Heap Area) : 객체들의 아파트
    • 역할: new 키워드로 생성된 모든 인스턴스(객체)와 배열이 저장되는 공간입니다.
    • 특징: 가비지 컬렉터(Garbage Collector)가 더 이상 참조되지 않는 객체를 찾아 메모리에서 해제합니다. 숫자 타입은 0, boolean은 false, 참조 타입은 null 등으로 자동 초기화가 진행됩니다. 모든 스레드가 공유하는 영역입니다.
  3. 메소드 영역 (Method Area or Static Area) : 클래스의 설계도 보관소
    • 역할: 클래스의 구조 정보(바이트 코드), 메소드 정보, 그리고 static으로 선언된 변수 및 필드가 저장됩니다.
    • 특징: JVM이 시작될 때 생성되며, 프로그램이 종료될 때까지 유지됩니다. 모든 스레드가 공유합니다.

코드로 보는 메모리 할당의 순간들

아래 예제 코드를 통해 각 요소가 어느 시점에 어떤 메모리 영역에 할당되는지 살펴보겠습니다.

public class MemoryExample {
    static int staticField = 10; // static 필드
    int instanceField = 20;      // 인스턴스 필드

    static void staticMethod() {
        System.out.println("Static Method");
    }

    void instanceMethod() {
        System.out.println("Instance Method");
    }

    public static void main(String[] args) {
        int localVar = 30; // 지역 변수

        // 1. 클래스 멤버 접근
        MemoryExample.staticMethod();
        System.out.println(MemoryExample.staticField);

        // 2. 인스턴스 생성 및 멤버 접근
        MemoryExample example = new MemoryExample(); // 객체 생성
        example.instanceMethod();
        System.out.println(example.instanceField);
    }
}

1단계: 클래스 로드 시점 - 설계도를 펼치는 순간

JVM이 MemoryExample.class 파일을 읽어들일 때 발생합니다.

  • staticFieldstaticMethod() 코드, instanceMethod() 코드는 **메소드 영역(Method Area)**에 올라갑니다.
  • 이들은 프로그램 전체에서 공유되며, 언제든 접근할 수 있는 상태가 됩니다.

2단계: main 메소드 실행 시점 - 작업의 시작

main 메소드가 호출되면 다음과 같이 메모리가 할당됩니다.

  • main 메소드를 위한 새로운 스택 프레임이 **스택 영역(Stack Area)**에 생성됩니다.
  • main 메소드 내부의 지역 변수인 localVar는 이 스택 프레임 안에 저장됩니다.
  • MemoryExample.staticMethod()를 호출하거나 staticField에 접근할 때는 이미 메소드 영역에 있는 코드를 실행하거나 값을 읽어올 뿐, 새로운 메모리 할당은 거의 없습니다.

3단계: 객체 생성 시점 - 실제 제품을 만드는 순간

new MemoryExample() 코드가 실행되는 순간입니다.

  • new 키워드에 의해 MemoryExample 객체가 **힙 영역(Heap Area)**에 생성됩니다.
  • 객체의 멤버 변수인 instanceField는 이 힙 영역의 객체 내부에 자리를 잡습니다.
  • main 메소드의 지역 변수인 example스택 영역에 생성되며, 힙에 생성된 객체의 **참조 주소(주소값)**를 저장합니다.
  • example.instanceMethod()를 호출하면, 메소드 영역에 있는 메소드 코드를 사용하되, 이 메소드 실행을 위한 새로운 스택 프레임이 스택 영역에 추가로 생성됩니다.

한눈에 보는 메모리 할당 요약

메모리 영역 저장되는 내용 예시 코드
메소드 영역 (Method Area) 클래스 코드, static 변수/메소드 staticField, staticMethod(), instanceMethod()
힙 영역 (Heap Area) new로 생성된 모든 객체와 배열, 인스턴스 변수 new MemoryExample(), instanceField
스택 영역 (Stack Area) 지역 변수, 매개변수, 객체의 참조 주소 localVar, example

결론

JVM이 메모리를 스택, 힙, 메소드 영역으로 나누어 관리하는 덕분에 우리는 효율적이고 안정적인 애플리케이션을 만들 수 있습니다.

  • 스택은 빠른 작업 처리를 위한 임시 공간으로,
  • 은 객체들이 오래도록 머무는 거주 공간으로,
  • 메소드 영역은 모두가 공유하는 공공 도서관처럼 작동합니다.