백엔드

Java 메모리 영역과 동작 원리

Newbie Developer 2025. 2. 14. 13:28

 

Java는 메모리 관리를 자동으로 수행하는 가비지 컬렉터(Garbage Collector, GC) 를 제공하여 개발자가 직접 메모리를 해제하지 않아도 됩니다. 하지만 효율적인 Java 애플리케이션을 개발하기 위해서는 Java의 메모리 구조를 이해하는 것이 중요합니다.

이번 글에서는 Java의 메모리 영역과 그 동작 방식을 자세히 살펴보겠습니다.


1. Java 메모리 구조 개요

Java 애플리케이션이 실행될 때, JVM(Java Virtual Machine) 은 여러 개의 메모리 영역을 관리합니다. JVM 메모리는 크게 Method Area, Heap, Stack, PC Register, Native Method Stack 으로 나뉩니다.

🔹 Java 메모리 영역 구성

메모리 영역 설명
Method Area (메서드 영역, 클래스 영역) 클래스 정보, static 변수, 메서드 코드 저장
Heap (힙 영역) 객체 및 인스턴스 변수 저장
Stack (스택 영역) 메서드 호출 시 지역 변수 및 참조 변수 저장
PC Register (PC 레지스터) 현재 실행 중인 명령어 주소 저장
Native Method Stack (네이티브 메서드 스택) JNI(Java Native Interface) 실행 시 사용

2. 메모리 영역 상세 분석

각 영역이 어떤 역할을 하며, 어떻게 동작하는지 살펴보겠습니다.

1) Method Area (메서드 영역)

"클래스 정보, static 변수, 메서드 코드가 저장되는 영역"

  • JVM이 로드한 클래스의 메타데이터(Class Metadata) 저장
  • static 변수상수(Constant Pool) 저장
  • 메서드 코드(바이트코드)와 JIT(Just-In-Time) 컴파일된 코드 저장
  • JVM이 종료될 때까지 유지되는 데이터 포함

예제 코드

class Example {
    static int staticVariable = 100; // 메서드 영역에 저장됨
}

➡️ staticVariable은 Method Area에 저장됩니다.


2) Heap (힙 영역)

"객체(인스턴스)와 인스턴스 변수가 저장되는 영역"

  • new 키워드를 통해 생성된 객체와 배열이 저장됨
  • GC(Garbage Collector) 가 주기적으로 사용하지 않는 객체를 제거
  • Young Generation, Old Generation(또는 Tenured), Permanent Generation(또는 Metaspace) 로 나뉨

예제 코드

class Person {
    String name; // Heap에 저장됨
    int age;     // Heap에 저장됨

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("Alice", 25); // Heap에 저장됨
    }
}

➡️ new Person("Alice", 25) 을 호출하면 힙 영역에 Person 객체가 생성됩니다.


3) Stack (스택 영역)

"메서드 실행 시 생성되는 지역 변수 및 참조 변수가 저장되는 영역"

  • 쓰레드(Thread) 별로 독립적인 Stack을 가짐
  • 메서드 호출 시 프레임(Call Stack Frame)이 생성되고, 종료되면 제거됨
  • 지역 변수 및 참조 변수가 저장됨

예제 코드

public class Main {
    public static void main(String[] args) {
        int x = 10;        // Stack에 저장
        int y = 20;        // Stack에 저장
        Person p = new Person("Alice", 25); // p 변수(참조값)는 Stack에, 객체는 Heap에 저장됨
    }
}

➡️ x, y, p는 Stack 영역에 저장되며, p가 참조하는 객체는 Heap 영역에 저장됩니다.


4) PC Register (PC 레지스터)

"현재 실행 중인 JVM 명령어의 주소를 저장하는 영역"

  • 각 쓰레드별로 하나의 PC Register를 가짐
  • JVM이 실행할 현재 바이트코드 명령어 주소를 저장
  • 네이티브 코드(Java가 아닌 언어) 실행 시는 사용되지 않음

동작 방식

public class Main {
    public static void main(String[] args) {
        int a = add(5, 10);
    }

    public static int add(int x, int y) {
        return x + y;
    }
}

➡️ add() 메서드를 실행할 때, PC Register에는 add() 메서드의 현재 실행 위치가 저장됩니다.


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

"JNI(Java Native Interface)로 호출된 네이티브 코드(C, C++) 실행 시 사용"

  • JVM이 아닌 네이티브 코드(C, C++) 실행을 위한 공간
  • OS 및 네이티브 라이브러리와의 연동을 위해 사용됨

JNI 예제 코드

public class Main {
    static {
        System.loadLibrary("nativeLibrary"); // C/C++ 라이브러리 로드
    }

    public native void nativeMethod(); // 네이티브 메서드 선언

    public static void main(String[] args) {
        new Main().nativeMethod(); // 네이티브 메서드 실행 (Native Stack 사용)
    }
}

➡️ nativeMethod() 실행 시 Native Method Stack이 사용됩니다.


3. Java 메모리 관리 및 GC (Garbage Collection)

🔹 JVM Heap 영역의 구조

JVM의 Heap 영역은 GC(Garbage Collector) 에 의해 관리됩니다.

영역 설명
Young Generation 새롭게 생성된 객체가 저장됨 (Eden + Survivor)
Old Generation 오랫동안 참조된 객체가 저장됨
Metaspace (Java 8 이후) 클래스 메타데이터 저장

🔹 가비지 컬렉션의 과정

  1. Young Generation (Minor GC) : Eden 영역이 가득 차면 사용되지 않는 객체 제거
  2. Old Generation (Major GC, Full GC) : 오래된 객체를 대상으로 GC 실행
  3. Finalization 및 메모리 해제 : 객체의 finalize() 호출 후 제거됨

GC 로그 활성화

java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -jar app.jar

➡️ GC 로그를 출력하여 메모리 사용량을 모니터링할 수 있습니다.


4. 메모리 최적화 방법

  • 객체 재사용 (Object Pool, Singleton) : 불필요한 객체 생성을 줄임
  • 컬렉션 초기 크기 설정 : ArrayList, HashMap 등의 초기 크기를 적절히 설정
  • WeakReference 사용 : 캐시 데이터는 WeakReference를 사용하여 GC 대상이 되도록 설정
  • GC 튜닝 : -Xms, -Xmx 옵션으로 힙 크기 조정

5. 결론

Java의 메모리 영역을 이해하면 효율적인 메모리 관리와 성능 최적화가 가능합니다. 특히 GC 튜닝 및 메모리 프로파일링을 통해 메모리 누수를 방지하고 애플리케이션의 안정성을 높일 수 있습니다.

핵심 정리

  • Method Area: 클래스 정보, static 변수 저장
  • Heap: 객체 저장 (GC 대상)
  • Stack: 메서드 호출 시 지역 변수 저장
  • PC Register: 현재 실행 중인 명령어 위치 저장
  • Native Stack: JNI 메서드 실행 시 사용

효율적인 Java 애플리케이션 개발을 위해 메모리 구조를 이해하고 최적화하는 습관을 가지세요! 🚀