Java

자바의 런타임 데이터 영역에 대하여

지화자_ 2024. 6. 15. 21:33

이번 포스팅에선 JVM의 메모리 영역인 런타임 데이터 영역(Runtime Data Area)에 대해 알아보겠습니다

 

앞선 포스팅을 통해 Java가 실행되는 과정을 학습한 바 있다.

이제 JVM의 메모리 공간(Runtime Data Area)에 대한 학습을 통해 메모리 관리에 대해 이해해보자.

 

Runtime Data Areas란?

JVM이 프로그램을 실행하는 동안 사용하는 메모리 영역

위에 그림과 같이 Runtime Data Areas는 크게 5가지 (메서드 영역, 힙 영역, 스택 영역, PC 레지스터, 네이티브 메서드 스택) 로 나눌 수 있다.

 

메서드 영역(Method Area)

클래스가 처음 로드될 때, 클래스 로더가 파일을 읽고 메서드 영역에 클래스 정보를 저장한다.

static으로 선언된 변수들이 이곳에 저장된다. 

 

힙 영역(Heap Area)

힙 영역은 객체와 배열을 저장하는 곳이다. 모든 객체는 힙 영역에 동적(new 키워드를 통해 생성된 객체)으로 할당된다. 또한 GC(Garbage Collector)를 통해 더 이상 사용되지 않은 객체를 불필요한 메모리로 판단하여 제거한다.

 

 

위 두 가지 영역은 특징적으로 모든 스레드가 공유하는 영역으로 멀티 스레드 프로그래밍을 할 때 동기화에 주의해야한다

더보기

여러 스레드가 동시에 공유 자원에 접근하거나 수정할 때 발생할 수 있는 문제들

Race Condition, Deadlock, Livelock 


 

반면 아래 소개될 세 가지 영역은 스레드가 생성될 때 개별적으로 생성되는 독립적인 영역으로 서로 다른 스레드가 충돌하지 않는다.

 

스택 영역(Stack Area)

각 스레드마다 생성되는 메모리 영역으로 메서드 호출 시 프레임(Frame)이라는 구조체가 쌓이며, 메서드 종료 시 해당 프레임은 스택에서 제거된다. 각 프레임에는 로컬 변수(local Variable Array), 연산 스택(Operand Stack), 메서드 호출 정보, 상수 풀 참조 등이 포함된다.

 

프레임(Frame)의 구성 요소

로컬 변수 배열

메서드가 호출될 때, 해당 메서드의 매개변수와 로컬변수가 로컬변수 배열에 저장된다. 여기서 한 가지 알아둘 점은 인스턴스 메서드와 정적 메서드의 로컬 변수 배열의 첫 번째 인덱스가 다르다는 것이다. 인스턴스 메서드의 경우, 현재 인스턴스에 대한 참조 (this) 가 첫 번째 인덱스에 해당한다. 하지만 정적 메서드의 경우 그렇지 않다.

public class Example {
    private int field1;
    private int field2;

    public void instanceMethod(int param1, int param2) { //인스턴스 메서드
        int localVar1 = param1 + param2;
        int localVar2 = localVar1 * 2;
    }

    public static void staticMethod(int param1, int param2) { //정적 메서드
        int localVar1 = param1 + param2;
        int localVar2 = localVar1 * 2;
    }
}

instanceMethod의 로컬 변수 배열 구성

this param1 param2 localVar1 localVar2

 

staticMethod의 로컬 변수 배열 구성

param1 param2 localVar1 localVar2

 

 

연산스택(Operand Stack)

연산 스택은 메서드 내에서 연산을 수행하기 위해 필요한 숫자와 연산 결과를 임시로 저장하는 스택으로 LIFO(Last In First Out)의 구조를 갖는다.  

 

위의 예시를 생각했을 때 param1 값과 param2 값이 연산스택에 들어간 뒤 + 연산을 거쳐 스택에 저장한 뒤 이를 지역변수 loca1Var1에 저장되는 형태이다.

 

 

상수 풀 참조(Current Class Constatnt Pool Reference)

클래스 파일의 상수 풀을 참조하고 관리한다. 상수 풀은 클래스 파일의 일부분으로 다양한 상수 값, 메서드 및 필드 참조, 문자열 리터럴 등을 포함한다.

 

다음 코드를 통해 상수 풀 참조가 어떻게 작동하는 지 확인해보자.

public class ConstantPoolExample {
    public static void main(String[] args) {
        String greeting = "Hello, World!";
        int number = 42;
        int sum = number + 58;
        System.out.println(greeting);
        System.out.println(sum);
    }
}

 

 

위 코드를 바이트 코드로 변환 시 아래와 같은 코드로 나타낼 수 있다.

#에 해당하는 부분을 주목하자

 

#1 = Methodref #6.#22 // java/lang/Object."<init>":()V:

  • #1은 java/lang/Object 클래스의 <init> 메서드를 참조합니다.

#2 = String #23 // Hello, World!: 

  • #2는 문자열 "Hello, World!"를 참조합니다.

#3 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/PrintStream;:

  • #3은 java/lang/System 클래스의 out 필드를 참조합니다.

#4 = Methodref #26.#27 // java/io/PrintStream.println:(Ljava/lang/String;)V:

  • #4는 java/io/PrintStream 클래스의 println 메서드를 참조합니다.

PC 레지스터(PC Registers)

현재 실행되고 있는 명령어의 주소를 저장함. 이는 JVM이 다음에 실행할 명령어를 결정하는 데 사용됨.

 

동작 예시 (순차실행)

0x1000: LOAD R1, 0x2000  ; R1 레지스터에 메모리 주소 0x2000의 값 로드
0x1004: ADD R1, R1, #1   ; R1 레지스터 값에 1 추가
0x1008: STORE R1, 0x2000 ; R1 레지스터 값을 메모리 주소 0x2000에 저장

 

 

Native Method Stacks

C나 C++로 사용된 메서드를 호출할 때 쓰이는 스택 영역으로 JNI(Java Native Interface)를 통해 호출됨.

 

 

출처