🐍Python | Django

[Python] Python의 메모리 관리

이줭 2022. 5. 3. 23:36
728x90

파이썬에서는 모든 것이 객체이고, 동적 메모리 할당이 파이썬 메모리 관리의 기초이다.

 

동적 메모리 할당이랑 런타임 시 메모리가 할당되는 것으로 객체가 더 이상 필요하지 않으면 파이썬 메모리 관리자가 자동으로 객체에서 메모리를 회수한다.

 

메모리 관리는 reference countinggenerational 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://velog.io/@swhan9404/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B4%80%EB%A6%AC%EA%B3%BC-%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-%EC%BD%94%EB%93%9C

https://medium.com/dmsfordsm/garbage-collection-in-python-777916fd3189

728x90

'🐍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