자바 메모리 구조 정리: Stack과 Heap을 정확히 이해하기

revo·2026년 2월 9일

자바

목록 보기
6/30


자바를 공부할 때 스택과 힙을 많이 들어보았다.
하지만 이 두 개념을 정확히 구분해서 설명하지 못하는 경우가 많다.
나 역시 한동안 “객체는 힙에 있다”, “변수는 스택에 있다” 같은 표현을 아무 생각 없이 받아들였다가 메서드 호출, 객체 전달, 값 변경이 엇갈리는 순간마다 계속 헷갈렸다.


자바 메모리 모델에서 Stack과 Heap의 역할

자바 프로그램은 JVM 위에서 실행되며, JVM은 실행 중인 데이터를 여러 메모리 영역에 나누어 관리한다.
그중에서도 실제 코드를 이해하는 데 핵심이 되는 영역이 Stack과 Heap이다.

  • Stack: 메서드 호출과 함께 생성되는 실행 단위 공간
  • Heap: new로 생성된 객체와 배열이 저장되는 공간

이 둘을 단순히 “여기엔 변수, 저기엔 객체”로 외우면 반드시 한 번은 개념이 무너진다.
중요한 건 무엇이 저장되느냐보다, 언제 생성되고 언제 사라지느냐다.


Stack은 메서드 호출을 기록하는 공간이다

Stack은 메서드가 호출될 때마다 하나의 스택 프레임(Stack Frame)을 생성한다.
이 프레임 안에는 다음과 같은 정보가 들어간다.

  • 매개변수
  • 지역 변수
  • 메서드 실행 중 필요한 임시 값
  • 메서드가 끝난 뒤 돌아갈 위치 정보

메서드가 끝나면 해당 스택 프레임은 통째로 제거된다.
이 과정은 매우 빠르고, 개발자가 직접 관리하지 않는다.

중요한 특징은 다음과 같다.

  • Stack은 스레드마다 독립적이다.
  • Stack에 있는 데이터는 메서드 생명주기와 정확히 일치한다.
  • 메서드가 끝나면, 그 안의 지역 변수는 더 이상 접근할 수 없다.

Heap은 객체가 실제로 살아 있는 공간이다

Heap은 new 키워드를 통해 생성된 객체와 배열이 저장되는 공간이다.
Stack과 달리, Heap에 있는 객체는 메서드가 끝나도 바로 사라지지 않는다.

Heap의 핵심 특징은 다음과 같다.

  • 여러 스레드가 공유한다.
  • 객체의 생명주기는 메서드와 무관하다.
  • 더 이상 참조되지 않는 객체는 GC(Garbage Collector)의 대상이 된다.

여기서 자주 생기는 오해가 하나 있다.

“객체는 힙에 있고, 변수는 스택에 있다”

이 표현은 반은 맞고 반은 틀리다.
정확히 말하면,

  • 객체는 힙에 있다
  • 객체를 가리키는 참조 변수는 스택에 있다

기본형과 참조형은 메모리에서 다르게 동작한다

기본형(Primitive Type)의 경우

int a = 10;
int b = a;
b = 20;
  • ab는 Stack에 값 자체를 가진다.
  • b = a는 값 복사다.
  • b를 바꿔도 a는 영향을 받지 않는다.

즉, 기본형은 값 복사다.


참조형(Reference Type)의 경우

class Person {
    int age;
}

Person p1 = new Person();
p1.age = 10;

Person p2 = p1;
p2.age = 20;

이 코드에서 실제로 일어나는 일은 다음과 같다.

  • new Person() → Heap에 객체 생성
  • p1 → Stack에 생성되고, Heap 객체의 주소값(참조값)을 가진다
  • p2 = p1 → 참조값이 복사된다
  • p1p2같은 객체를 가리킨다

따라서 p2.age = 20
“p2만 바꾸는 것”이 아니라, 힙에 있는 하나의 객체를 바꾸는 것이다.

여기서 핵심은 이것이다.

참조형의 대입은 객체 복사가 아니라, 참조값 복사다.


메서드 파라미터 전달이 헷갈리는 이유

자바는 무조건 Call by Value다.
다만, 참조형의 경우 “값”이 곧 참조값일 뿐이다.

객체 내부 변경은 가능한 경우

void change(Person p) {
    p.age = 99;
}

Person p = new Person();
p.age = 10;
change(p);
  • p의 참조값이 복사되어 메서드로 전달된다
  • 같은 객체를 가리키고 있으므로, 객체 내부 필드 변경은 반영된다

참조 자체를 바꾸는 것은 불가능한 경우

void swap(Person p) {
    p = new Person();
    p.age = 77;
}

Person p = new Person();
p.age = 10;
swap(p);
  • p의 참조값이 복사된 것은 동일하다
  • 하지만 p = new Person()메서드 안의 지역 변수만 다른 객체를 가리키게 한다
  • 호출한 쪽의 p는 전혀 영향을 받지 않는다

이 두 예제의 차이는 객체를 수정했는지, 아니면 참조 변수를 다른 객체로 바꿨는지의 차이다.


객체는 언제 사라지는가 (GC 관점)

객체는 다음 조건을 만족하면 GC 대상이 된다.

  • 어떤 Stack 변수, static 변수, 다른 객체 필드에서도 더 이상 해당 객체를 참조하지 않을 때.
Person p = new Person();
p = null;

이 순간 객체는 즉시 삭제되는 것이 아니라, “더 이상 필요 없는 객체”로 판단 대상이 된다.

언제 실제로 제거될지는 GC의 판단이다.


핵심 정리

  • Stack은 메서드 실행 기록 공간
  • Heap은 객체가 실제로 존재하는 공간
  • 자바는 항상 값 복사
  • 참조형은 참조값이 복사될 뿐
  • 객체의 내용은 바꿀 수 있지만, 호출한 쪽의 참조 자체는 바꿀 수 없다

0개의 댓글