본문 바로가기

Java/Java SE, EE

[Java/Java SE, EE] 자바 메모리 구조, JVM 메모리 영역(Runtime Data Area)

메모리(RAM, Random Access Memory)

메모리는 컴퓨터에서 주기억장치에 속하며, 애플리케이션을 실행(로딩)하고 연산을 수행하는데 필요한 변수 등을 저장하는 등의 역할을 수행합니다. 프로그램이 실행되기 위해서는 먼저 메모리에 로드되어야 합니다. 또한 프로그램이 실시간으로 실행되면서 필요한 변수들을 저장하기 위한 공간도 메모리에서 확보를 해야합니다.

자바 메모리 구조, JVM 메모리 영역(Runtime Data Area)

자바 프로그램(*.exe)이 실행되면 JVM(Java Virtual Machine)은 운영체제로부터 할당 받은 메모리 영역을 용도에 따라 나누어 관리합니다. 운영체제가 메모리 영역에 해당하는 램을 관리하고 있으며, JVM은 운영체제로부터 램의 일부를 할당받아 사용합니다.

아래는 JVM이 관리하는 메모리 영역을 간략하게 분류하였습니다.

  • JVM 메모리 영역(Runtime Data Area)
    • 메소드 영역(Method Area)
      • 런타임 상수 풀(Runtime constant pool)
      • 필드(Field)
      • 메소드(Method)
      • 정적 변수(Static variable)
      • 생성자(Constructor) 등
    • 스택 영역(Stack Area)
    • 힙 영역(Heap Area)

메소드 영역(Method Area, Class Area, Code Area, Static Area)

JVM의 메소드 영역은 자바 클래스와 관련된 메모리 영역입니다(따라서 클래스 영역-Class Area 등으로도 부릅니다). 메소드 영역을 살피기 전에 자바의 클래스 로더(Class loader)에 대해서 숙지하고 있어야 합니다.

클래스 로더는 자바 JRE 모듈에 속하며, 프로그램이 실행되고 있는 런타임 중에 각각의 클래스를 JVM에 동적 로드합니다. 클래스 로더가 로드하는 클래스는 자바 컴파일러에 의해 생성된 *.class 바이트(Byte) 파일입니다. 이 과정을 클래스 로딩(Class loading)이라고 합니다

자바는 모든 코드가 클래스 하에 구현됩니다. 자바 프로그램에서 어떤 코드가 실행된다는 것은 해당 코드를 구현하는 클래스(*.class)가 클래스 로더(Class loader)에 의해서 클래스 로딩(Class loading)되는 과정을 거치게 됩니다.

클래스 로더가 클래스를 불러오면, 각 클래스에 대한 정보를 다음 메모리 구조로 나누어 관리하게 됩니다.

  • 런타임 상수 풀(Runtime constant pool)
  • 필드(Field)
  • 메소드(Method)
  • 정적 변수(Static variable)
  • 생성자(Constructor) 등

또한 메소드 영역은 모든 스레드에 걸쳐 동일한 메모리를 공유합니다. 즉, 클래스 로더가 어떤 클래스를 불러오면, 프로그램에서 실행되는 모든 스레드에 공유되는 메소드 영역으로부터 해당 클래스의 정보에 접근 할 수 있습니다(이때문에 공유 메모리 영역이라고도 부릅니다).

스택 영역(Stack Area)

JVM의 스택 영역은 프로그램에서 실행되는 각 스레드마다 별도로 존재합니다. 만약 우리가 개발한 자바 프로그램에서 별도의 스레드를 생성하지 않았다면, 메인 스레드 하나만 존재하기 때문에 스택 영역은 하나뿐입니다.

스택 영역은 프로그램이 메소드를 호출 할 때마다 프레임(Frame)을 추가(Push)하고 메소드가 종료될 때마다 가장 상위 프레임을 제거(Pop)하는 방식으로 관리됩니다. 이름과 동작 방식에서 알 수 있듯이 메소드를 스택 자료구조 형태로 관리하는 메모리 영역에 해당합니다. 자바 프로그램이 실행 도중 예외가 발생하면 printStackTrace() 함수를 사용하여 이 스택 영역에 존재하는 프레임을 출력 할 수도 있습니다.

각각의 프레임 내부에는 로컬 변수 스택이 존재합니다. 로컬 변수 스택은 메소드 내에서 선언된 로컬 변수들에 대해서 스택 자료구조로 관리하는 메모리 영역입니다. 아래 코드는 어떤 메소드 내에서 로컬 변수 스택에 변수들이 추가(Push)되고 삭제(Pop)되는 과정을 나타낸 것입니다.

// Push(a) - /a
int a = 100;
// Push(b) - /a/b
int b = 0;

if (a == 100) {
   // Push(c) - /a/b/c
   int c = 5;
   b = a * c;
   
   // Pop(c) - /a/b
}

// Push(d) - /a/b/d
int d = b + a;

// Pop(d) - /a/b
// Pop(b) - /a
// Pop(a) - /

힙 영역(Heap Area)

JVM의 힙 영역은 객체(인스턴스)와 배열 등 참조(Reference) 타입에 대한 메모리 영역에 해당합니다. 객체는 코드에서 new 키워드를 사용하여 클래스로부터 동적으로 생성된 인스턴스를 부르는 지칭입니다. new 키워드로 생성된 객체는 힙 영역에 할당 되며, 프로그램에서 실행되는 모든 스레드에서 접근할 수 있습니다.

힙 영역에 할당된 객체에 대한 주소는 다른 JVM의 스택 영역 등에서 변수와 필드에 의해 참조 될 수 있습니다. 만약 해당 힙 영역의 데이터를 참조하는 다른 메모리 영역의 변수나 필드가 더 이상 존재하지 않으면, 이 데이터는 더 이상 사용될 가능성이 없다고 판단되어 JVM의 가비지 콜렉터(GC, Garbage Collector)에 의해 힙 영역에서 해지(삭제) 됩니다. 자바와 같이 JVM 등에 의해서 힙 영역을 자동으로 관리하는 언어를 매니지드(Managed) 언어라고 부르며, C와 C++ 등 힙 영역에 할당된 동적 객체를 프로그래머가 직접 해제해야 하는 언어를 언매니지드(Un-managed) 언어라고 부릅니다.