본문 바로가기
python

[Python] 파이썬 GC(Garbage Collector)

by 바디스 2022. 9. 29.

1. Garbage Collection(가비지 컬렉션)이란?

프로그램을 개발 하다 보면 유효하지 않은 메모리인 가바지(Garbage)가 발생합니다. C언어의 경우 개발자가 malloc() ,free()라는 함수를 통해 직접 메모리를 해제해주어야 합니다.가비지 컬렉션이란 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역(Garbage)을 해제하는 기능입니다. 

 

2. Python에서 GC

파이썬에선 기본적으로 참조 횟수가 0이 된 객체를 메모리에서 해제하는 레퍼런스 카운팅 방식을 사용합니다. 하지만, 참조 횟수가 0은 아니지만 도달할 수 없지만, 상태인 reference cycles(순환 참조)가 발생했을 때는 별도의 알고리즘을 통해 상황을 해결합니다.

 

2.1. 레퍼런스 카운팅

모든 객체는 참조 당할 때 레퍼런스 카운터를 증가시키고 참조가 없어질 때 카운터를 감소시킵니다. 이 카운터가 0이 되면 객체가 메모리에서 해제합니다. 어떤 객체의 레퍼런스 카운트를 보고 싶다면 sys.getrefcount()로 확인할 수 있습니다.

>>> import sys
>>> a = 'hello'
>>> sys.getrefcount(a)
2
더보기

왜 참조 횟수가 2? 하나는 variable을 생성하는 것이고 두 번째는 변수 a를 sys.getrefcount() 함수에 전달할 때 카운트 됩니다.

 

2.2. 순환 참조

- 자기 자신을 참조하는 경우

>>> a = []
>>> a.append(a)
>>> del a

a의 참조 횟수는 1이지만 이 객체는 더 이상 접근할 수 없으며 레퍼런스 카운팅 방식으로는 메모리에서 해제될 수 없습니다.

 

- 서로를 참조하는 경우

>>> a = Foo()  # 0x60
>>> b = Foo()  # 0xa8
>>> a.x = b  # 0x60의 x는 0xa8를 가리킨다.
>>> b.x = a  # 0xa8의 x는 0x60를 가리킨다.
# 이 시점에서 0x60의 레퍼런스 카운터는 a와 b.x로 2
# 0xa8의 레퍼런스 카운터는 b와 a.x로 2다.
>>> del a  # 0x60은 1로 감소한다. 0xa8은 b와 0x60.x로 2다.
>>> del b  # 0xa8도 1로 감소한다.

이 상태에서 0x60.x와 0xa8.x가 서로를 참조하고 있기 때문에 레퍼런스 카운트는 둘 다 1이지만 도달할 수 없는 가비지가 됩니다.

 

 

3. 가비지 컬렉션 작동 방식

3.1. 세대기반(Generational Garbage Collector)

파이썬의 가비지 컬렉터는 내부적으로 generation(세대)과 threshold(임계값)로 가비지 컬렉션 주기와 객체를 관리합니다.

가비지 콜렉터는 파이썬의 모든 객체를 추적하고 있습니다. 만일 객체의 수가 임계값을 넘으면 GC가 작동합니다. 이때 첫 세대가 시작되고, 여기서 생존한 객체는 다음(older) 세대로 넘어갑니다. 가비지 콜렉터는 총 3개의 세대를 가지고 있고, 쓰레기 수집 과정에서 생존한 객체는 다음 세대로 넘어갑니다.

 

>>> gc.get_threshold()
(700, 10, 10)

각각 threshold 0, threshold 1, threshold 2를 의미하는데 n세대에 객체를 할당한 횟수가 threshold n을 초과하면 가비지 컬렉션이 수행되며 이 값은 변경할 수 있습니다.

0세대의 경우 메모리에 객체 수가 threshold 0을 초과하면 실행됩니다. 다만 그 이후 세대부터는 조금 다른데 0세대 가비지 컬렉션이 일어난 후 0세대 객체를 1세대로 이동시킵니. 이 1세대 카운터가 threshold 1을 초과하면 그때 1세대 가비지 컬렉션이 작동합니다. 0세대 가비지 컬렉션이 객체 생성 700번만에 일어난다면 1세대는 7000번만에, 2세대는 7만번만에 일어납니다.

 

3.2. 순환 참조는 어떻게 해결하는가?

먼저 순환 참조는 컨테이너 객체(e.g. tuple, list, set, dict, class)에 의해서만 발생할 수 있음을 알아야 합니다. 컨테이너 객체는 다른 객체에 대한 참조를 보유할 수 있습니다. 그러므로 정수, 문자열은 무시한 채 관심사를 컨테이너 객체에만 집중합니다.

 

가비지 컬렉션은 수행하기전에 순환 참조 탐지 알고리즘을 통하여 특정 세대에서 도달할 수 있는 객체를 찾는데 도달 할수 있는 객체는 세대를 이전 시키고 도달할 수 없는 객체는 메모리에서 해제합니다.

 

  • 객체에 gc_refs 필드를 레퍼런스 카운트와 같게 설정합니다.
  • 각 객체에서 참조하고 있는 다른 컨테이너 객체를 찾고, 참조되는 컨테이너의 gc_refs를 감소시킵니다.
  • gc_refs가 0이면 그 객체는 컨테이너 집합 내부에서 자기들끼리 참조하고 있다는 뜻이 됩니다.
  • 그 객체를 unreachable(도달할 수 없는 코드) 하다고 표시한 뒤 메모리에서 해제합니다.

댓글