변수는 이름표, 객체는 실제 물건이다.”
스코프(scope)와 객체(object)를 분리해서 이해하면 메모리 관리가 보인다.
“지역변수가 끝나면 객체도 끝난다?”
“불변 객체는 한 번 만들면 프로그램 종료까지 남는다?”
프로그래밍 Q&A에서 꾸준히 오해가 반복되는 주제다.
여기에 Java 와 Python(CPython) 을 나란히 두고 객체와 변수의 생존 주기를 정리했다.
| 구분 | 저장 위치 | 살아 있는 조건 | 흔한 오해 |
|---|---|---|---|
| 클래스 변수(static field) | 메서드영역(HotSpot 기준) | Class 객체가 ClassLoader에 탑재되는 동안(JVM 종료까지 남는 경우가 많음) | “메서드(콜스택)가 끝나면 사라진다” |
| 인스턴스 변수 | 힙(Heap) | 해당 객체에 강한 참조가 1개 이상 | “메서드 끝나면 사라진다” |
| 지역 변수 | 스택(Stack Frame) | 메서드 실행 중 | 여기서만 대체로 맞음 — 다만 참조 변수가 가리키던 객체는 다른 곳에서 참조 중이면 생존 |
Point!
변수와 객체를 분리해서 보자.
스택에 있던 참조 변수가 사라져도, 힙에 있던 객체는 여전히 살아 있을 수 있다.
1. 참조 카운팅
2. 세대별 순환-GC
| 스코프 위치 | 예시 | 언제 사라질까? |
|---|---|---|
| 지역 스코프 | 함수 안 obj | 함수 리턴 → 참조 0 → 즉시 free |
| 인스턴스/클래스 속성 | self.attr, Cls.attr | 속성을 지닌 객체가 참조되는 한 |
| 모듈 전역 | config = {...} | 모듈이 sys.modules에 남아 있는 한 (보통 인터프리터 종료까지) |
| 인터닝 상수 | 0, "", True | CPython 내부 캐시에 보존, 인터프리터 종료 시 해제 |
“불변 == 영구”가 아니다!
작은 정수·짧은 문자열이 우연히 오래 남는 건 인터닝 캐시 덕분이다.
참조가 0이면 불변 객체라도 당연히 소멸한다.
import gc
class Demo:
def __init__(self, name): self.name = name
def __del__(self):
print(f"[GC] {self.name} collected")
def make(): # 지역 스코프
a = Demo("temp") # refcount = 1
b = a # refcount = 2
print("함수 끝…")
make() # a, b 모두 스코프 밖 → refcount 0
gc.collect() # 순환 참조 없지만, 강제로 GC
예상 출력
함수 끝…
[GC] temp collected
| 항목 | Java | Python (CPython) |
|---|---|---|
| 메모리 관리 | Tracing GC (Mark-Sweep, G1 등) | 참조 카운팅 + 순환-GC |
| “즉시 소멸” 가능? | ❌ (GC 싸이클 기다림) | ✅ (refcount 0) |
| 순환 참조 처리 | 기본 GC가 해결 | 별도 순환-GC 패스 |
| 불변 객체 최적화 | String 상수 풀, class constant pool | 작은 int·짧은 str 인터닝 |
| finalize / del | finalize() (Java 9 이후 deprecated) | del() (주의해서 사용) |