Memory and GC
Python은 refcount와 cyclic GC를 같이 쓰기 때문에, Go/Java 같은 tracing GC 언어와 메모리 감각이 꽤 다르다. 많은 객체는 refcount가 0이 되는 즉시 사라지지만, cycle은 별도 GC가 모아서 정리한다.
빠른 요약: CPython 메모리 모델의 핵심은 "즉시 회수되는 refcount"와 "cycle만 따로 도는 GC"의 조합이다. 그래서 자원 정리 시점은 꽤 예측 가능하지만, 큰 cycle graph와 pause 특성은 별도로 신경 써야 한다.
메모리 회수 흐름
cycle을 만드는 간단한 예
py
import gc
class Node:
def __init__(self, name: str) -> None:
self.name = name
self.other: Node | None = None
left = Node("left")
right = Node("right")
left.other = right
right.other = left
del left
del right
print("collected objects:", gc.collect())`left`와 `right`는 서로를 가리키는 cycle을 만든다. 외부 참조가 사라져도 refcount만으로는 0이 되지 않기 때문에 cyclic GC가 개입해야 한다.
pymalloc 감각
- CPython은 작은 객체를 위해 별도 allocator(
pymalloc)를 사용한다. - "객체가 해제되었다"와 "운영체제에 메모리가 즉시 반환되었다"는 같은 뜻이 아니다.
- 따라서 Python 프로세스 RSS가 기대만큼 바로 줄지 않는다고 해서 곧바로 leak로 단정하면 안 된다.
3.14에서 읽어야 할 흐름
- cyclic GC pause를 더 잘게 나누려는 방향
- latency-sensitive workload에서 stop-the-world 부담을 줄이려는 시도
- free-threaded/runtime 진화와 함께 메모리 모델도 점진적으로 조정되는 흐름
실전 연결
- large object graph
- file/socket lifecycle
- memory leak debugging
체크리스트
자원 정리는 GC에 기대지 않는다
파일, 소켓, 세션은 `with`나 명시적 close로 닫는 편이 안전하다. 메모리 회수와 자원 회수는 같은 문제가 아니다.
cycle을 의식한다
복잡한 graph 구조나 listener/reference back-pointer 패턴은 cycle을 만들기 쉽다.
RSS만 보지 않는다
allocator 특성 때문에 프로세스 메모리 사용량은 즉시 줄지 않을 수 있다. object count와 allocation pattern도 같이 본다.
GC debug 도구를 쓴다
`gc` 모듈, tracemalloc, object graph inspection을 같이 써야 leak와 allocator 특성을 구분하기 쉽다.