파이썬에서는 모든 것이 객체이고, 동적 메모리 할당이 파이썬 메모리 관리의 기초이다.
동적 메모리 할당이랑 런타임 시 메모리가 할당되는 것으로 객체가 더 이상 필요하지 않으면 파이썬 메모리 관리자가 자동으로 객체에서 메모리를 회수한다.
메모리 관리는 reference counting과 generational garbage collection 두 가지 방법으로 이루어 진다.
Reference Counting
파이썬에서는 객체를 만들 때마다 객체의 유형과 reference count를 private heap에 생성한다. 기본적으로 파이썬 객체의 reference count는 객체가 참조될 때마다 증가하고 객체의 참조가 해제될 때 감소하는 방식으로 작동하고, 이때 reference count가 0이 되면 객체의 메모리 할당이 해제된다.
reference count가 증가하는 경우로는 변수에 객체 할당, list에 추가 또는 class instance에 속성으로 추가 등 data structure에 객체 추가, 객체를 함수의 인수로 전달하는 경우 등이 있다.
sys모듈을 사용하여 특정 객체의 reference count를 확인할 수 있다.
import sys
a = 'ref'
# 참조횟수가 2로 나오는 이유는 변수에 할당된 이후 getrefcount()함수로 전달 되었기 때문이다.
print(sys.getrefcount(a)) # 2
b = [a]
print(sys.getrefcount(a)) # 3
c = {'first': a}
print(sys.getrefcount(a)) # 4
Generational Garbage Collection
파이썬에서는 reference counting 외에도 generational garbage collection이라는 방법을 사용한다.
왜 generational garbage collection이 추가적으로 사용될까?
만약 객체가 순환 참조를 하는 경우 어떤 상황이 일어나게 되는지 예를 들어보면
a = []
a.append(a)
del a
위의 상황에서 a의 reference count는 1이지만 해당 객체에는 더 이상 접근할 수 없으므로 reference counting 방식으로는 메모리에서 해제될 수 없다.
또 다른 예를 들어보면, 서로를 참조하는 경우 어떤 상황이 일어날까?
a = RefCls() # ref_cnt = 1
b = RefCls() # ref_cnt = 1
a.x = b # ref_cnt = 2
b.x = a # ref_cnt = 2
del a # ref_cnt = 1
del b # ref_cnt = 1
마지막 상태에서 a와 b의 reference count는 1이지만 더 이상 0에 도달할 수 없기에 메모리에서 해제될 수 없다. 이러한 유형의 문제를 reference cycle이라고 하며, reference counting으로는 해결할 수 없다.
garbage collector는 내부적으로 generation(세대)과 threshold(임계값)라는 개념을 통해 주기와 객체를 관리한다.
먼저, 세대는 0세대, 1세대, 2세대로 구분되고 최근에 생성된 객체는 0세대에서 시작하여, 오래된 객체일수록 2세대로 이동한다. 각 세 개에서, garbage collection을 수행하고 살아남는 객체들은 다음 세대로 올라간다.
각 세대별로 임계값이 존재하고 임계값은 변경할 수 있다. 각 세대에서 객체들의 reference count의 합이 해당 세대의 임계값을 초과하면 garbage collection이 수행되고, garbage collection이 수행될 때, 도달할 수 있는(reachable) 객체와 도달할 수 없는 객체(unreachable)를 구분하고, 도달할 수 없는 객체 집합을 찾는다.
도달할 수 있는 객체 집합은 다음 상위 세대로 합쳐지면서(0세대에서 수행되면 1세대로) reference count를 1 증가시키고, 도달할 수 없는 객체의 집합은 메모리에서 해제된다.
그렇다면 unreachable 한 객체의 집합은 어떻게 찾을 수 있을까?🤔
먼저 순환 참조는 컨테이너 객체(tuple, list, set, dict 등 in 키워드를 사용할 수 있는 객체)에 의해서만 발생할 수 있다.
따라서 컨테이너 객체 내부에 특정 필드를 reference count와 동일하게 설정하여 생성하고, 각 객체에서 참조하고 있는 다른 컨테이너 객체 수만큼 필드의 count를 감소시킨다.
순환 참조를 관리하기 필드가 0인데, 메모리 상에 존재하고 있다면 그 객체는 순환 참조 또는 서로를 참조하고 있다는 의미이기 때문에 unreachable 한 객체로 분류한 뒤 메모리에서 해제시킨다.
https://medium.com/dmsfordsm/garbage-collection-in-python-777916fd3189
'🐍Python | Django' 카테고리의 다른 글
[Python] iterable? iterator? (0) | 2022.05.08 |
---|---|
[Python] GIL (0) | 2022.05.07 |
[Django] Django MTV (0) | 2022.04.29 |
[Python] call by value? call by reference? (0) | 2022.04.28 |
[Python] 메타클래스(MetaClass) (0) | 2022.04.27 |