💻 Dev/Java & OOP

자바 코드의 메모리 영역(스택&힙)

현주먹 2024. 10. 29. 23:15

자바 프로그램에서 코드가 컴파일되어 JVM에서 실행되기까지 과정에서 JVM은 필요한 데이터들을 용도에 따라 나누어 관리한다.

 

JVM의 메모리 공간은 크게 Method 영역, Stack 영역, Heap 영역으로 구분되고 데이터 타입(자료형)에 따라 각 영역에 나눠서 할당된다.

 

 

 

메서드 영역(Method Area)

메서드 영역은 JVM이 시작될 때 생성되는 곳으로 클래스 변수(Static 변수), 생성자(constructor)와 메서드(method) 등이 저장되는 공간이다.

  • Static 영역이라고도 불리며 어느 곳에서나 접근이 가능하다.
  • 모든 스레드에서 공유되어 멀티 스레드 환경에서 동기화에 주의해야 한다.
  • 메서드 영역의 데이터는 프로그램이 종료될 때까지 메모리에 남아있다.
    그래서 프로그램이 종료될 때 까지 어디서든 사용이 가능하다.
  • 무분별하게 많이 저장할 경우 메모리 부족 현상이 일어날 수 있다.
public class Car {

    private static final int NAME_LENGTH_COND = 5;

    private final String name;
    private int position;

    public Car(final String name, final int position) {
            validateNameLength(name);
        this.name = name;
        this.position = position;
    }

    public void move() {
        position++;
    }

    private void validateNameLength(final String name) {
        if (name.length() > NAME_LENGTH_COND) {
            throw new IllegalArgumentException("이름은 5글자까지만 가능합니다.");
        }
    }
}

 

이 코드에서는 클래스 변수(NAME_LENGTH_COND), 생성자(Car), 메서드(move(), validateNameLength())가 메서드 영역에 저장된다.

 

 

스택(Stack)

스택 영역은 메서드 호출 시 생성되는 기본 자료형인 지역 변수와 매개 변수가 저장되는 영역이다.

  • 스레드마다 독자적인 스택 영역을 가진다.
  • 스택 프레임(Stack Frame) 단위로 메모리를 관리한다.
  • 메서드가 하나 호출될 때마다 스택 프레임이 생성되고 메서드가 끝나거나 예외가 터지면 제거된다.
  • 이 스택이 가득 찼을 때 발생하는 오류가 StackOverFlowError이다. (재귀 호출)

 

스택 프레임이란?

하나의 메서드에 필요한 메모리 덩어리를 묶어서 스택 프레임(Stack Frame)이라고 한다.
메서드를 호출하기 직전 스택 프레임을 스택영역에 생성한다.

 


위 코드의 Car 객체를 생성하고 move() 메서드를 실행한다면 3덩어리의 스택 프레임이 생성된다.

 

메서드가 호출될 때마다 생성되므로 Car 생성자 -> validateNameLength() -> move() 순서대로 쌓이고 메서드 실행이 끝나면 해당 스택프레임은 스택에서 제거된다.

 

name, position이 왜 저장돼?라고 생각할 수 있다. 그건 힙 스택 이후에 확인해 보자

 

힙(Heap)

힙 영역은 new 키워드를 통해 생성된 참조형 객체와 인스턴스 변수 그리고 배열이 저장된다.

  • 메서드 영역과 동일하게 모든 스레드에서 공유되는 곳이다.
  • 단, 힙 영역에 있는 객체들을 가리키는 참조를 위한 주소값은 스택에 포함된다.
  • 힙 영역은 스택 영역과 다르게 보관된 데이터가 호출이 끝나더라도 삭제되지 않고 유지된다.
  • 더 이상 참조하지 않는 객체 데이터는 가비지 컬렉터(GC)에 의해 제거된다.
public class Main {
    public static void main(String[] args) {
        Car car = new Car("kaki", 0);
        car.move();
    }
}

 

이 코드를 실행해 보자.

1. Car car = new Car("kaki", 0);

 

스택 영역에 생성자 메서드의 스택 프레임이 생성되고Car 객체에 대한 주소값을 가진다.
힙 영역에 Car 클래스의 인스턴스 변수, 값이 저장되고 스택 영역의 해당 주소값과 연결된다.

 

name과 position은 지역변수/매개변수가 아니라 인스턴스 변수인데 왜 스택에 저장되는가?

스택 영역의 nameposition은 실제 값이 아닌, 메서드의 매개변수와 참조가 잠시 저장되는 것이다.

public Car(final String name, final int position) {
            validateNameLength(name);
        this.name = name;
        this.position = position;
    }

 

즉, Car생성자 호출 시에, 생성자에 전달되는 매개변수 nameposition(즉, "CarName"0)은 생성자 호출 시 스택 영역에 저장되는 것이다.


nameposition매개변수로서 단순히 메서드 호출 동안에만 잠시 스택에 존재하는 것일 뿐이고 메서드 실행이 끝나면 스택에서 사라진다.

 

2. car.move();

 

생성자 호출이 끝났으니 Car 생성자 스택 프레임과 validateNameLength() 메서드 스택 프레임은 제거되고, mova() 메서드 스택 프레임이 추가된다.

여기서 this 라는 암묵적인 변수가 자동 생성되게 되는데, 이 this 변수는 자동으로 힙 영역에 있는 Car 객체를 가리키게 된다.
그래서 move() 메서드 안의 코드 position++이 동작하면 힙 영역에 있는 인스턴스 변수 position 값이 변하게 된다.

 

3. main() 메서드 종료

move() 메서드가 종료되고 main() 메서드 호출이 끝나면 main() 스택 프레임이 스택 영역에서 제거된다.

힙 영역의 Car 객체 데이터는 더 이상 참조하는 곳이 없을 때 GC에 의해 제거된다.

 

 

왜 각 종류의 변수들은 해당 메모리에 할당될까?

각 영역을 알아봐도 헷갈려서 따로 정리하게 됐다.

public class Calculator {
    int a = 1; //인스턴스 변수
    static int b = 3; //클래스 변수

    public static void main(String[] args) { //매개변수
        Calculator cal = new Calculator();
        int c = 3; //지역변수
    }
}

 

각 영역이 뭔진 알겠는데

클래스 변수메서드 영역에 저장되고, 인스턴스 변수힙 영역에 저장되고, 매개변수/지역변수스택에 저장될까?

 

인스턴스 변수(a)가 힙 영역에 저장되는 이유

생성 시기
: new Calculator()로 새로운 Calculator 객체를 생성할 때 a라는 인스턴스 변수가 생성된다.

 

왜 힙에 저장되는가?
인스턴스 변수는 각 객체마다 고유한 값을 가지므로 객체가 생성될 때마다 독립적으로 생성되어야 한다.
힙은 객체가 메모리에 할당되어 더 이상 참조되지 않을 때까지 제거되지 않고 유지된다.
인스턴스 변수는 객체와 함께 힙 영역에 저장되어 해당 객체가 참조될 때마다 접근할 수 있어야 하기 때문이다.

 

클래스 변수 (b)가 메서드 영역에 저장되는 이유

생성 시기
: 클래스 변수는 Car 클래스가 처음 메모리에 로드될 때 한 번 생성된다.

 

왜 메서드 영역에 저장되는가?
클래스 변수는 모든 인스턴스가 공유하는 값이다 즉, 여러 객체가 생성되더라도 단 하나의 값을 유지한다.
메소드 영역은 프로그램 종료 시까지 유지되므로 클래스 변수의 값도 프로그램 종료 시까지 유지되어야 하기 때문이다.

 

매개변수 (args), 지역 변수 (c)가 스택 영역에 저장되는 이유

생성 시기
: 매개변수는 메서드가 호출될 때 생성된다.

 

왜 스택에 저장되는가?
매개변수와 지역변수는 메서드나 코드 블록 내에서만 사용되고, 해당 코드 블록이 끝나면 더 이상 참조되지 않는 임시 데이터입니다.
스택은 메소드 호출 시 생성되는 지역 데이터의 임시 저장을 위해 사용되고, 두 변수의 데이터는 메서드가 끝나면 필요 없어지므로 스택에서 관리된다.

 

 

 

참고
JVM 메모리 구조 파헤쳐 보기 (Static, Stack, Heap)
그림으로 보는 자바 코드의 메모리 영역(스택 & 힙)