계기
면접을 준비하면서 프로젝트와 문법만 공부하고 파이썬의 내부구조에 대한 이해가 낮다는 것을 알게 되었다.
이참에 정리하면서 내부구조도 학습해야 겠다.
참고 사이트: https://medium.com/dmsfordsm/garbage-collection-in-python-777916fd3189
GC(Garbage Collector란?)
거의 모든언어에서 사용되며 GC를 통해 메모리를 직접 관리 해주면서 개발자가 직접 메모리를 관리하는 코드를 작성
하지 않게 됨으로써 생산성이 크게 향상되었다. 하지만 메모리를 자동으로 관리는 해주지지만 서비스, 환경이 달라도 동일하게 관리를 해서 최적화가 덜 되어 있는 현실이다.
메모리 관리해야하는 이유
프로그램의 실행에 프로세스의 실행의 필요한 메모리가 할당되어야 한다. (Stack, Data, Code와 같은 메모리 주소공간)
대부분의 시스템에서는 여러 개의 프로세스들이
서로 cpu를 차지하려고 경쟁하듯이 메모리를 가지고 경쟁하는 환경입니다.
예를 들어 발전소에서 전기를 생산하는 양은 한정되어 있는데 한 회사에서 필요 없는 일에 전기를 다 써버린다면
그 주변 회사나 가정집은 전기를 사용하지 못하게 되는 것 처럼
메모리사용은 한정되어 있기 때문에 관리를 해줘야 합니다.
과거의 메모리 관리 문제점
메모리 관리를 직접 해줘야 했을 때
1. 필요 없는 메모리를 비우지 않았을 때
사람은 누구나 실수하기 때문에 메모리 사용이 끝났는데도 비우지 않고
그게 누적되면 실제 동작하는 프로그램에 큰 문제가 생긴다.
2.사용중인 메모리 비웠을 때
사용중인 메모리를 비우고 다른 곳에서 그 메모리를 접근하려고 한다면 프로그램은 중단 되거나
메모리 데이터가 손상될 수도 있다.
이러한 문제를 해결하기 위해 나온 것이 GC 즉 자동 메모리 관리 시스템이 생겼다.
Python의 Garbage Collection 동작 방식
- 레퍼런스 카운팅
- 세대별 가비지 컬렉션
Refernce Counting
Python 객체의 reference count는 객체가 참조될 때 마다 증가하고
객체의 참조가 해제될 때 감소한다. 객체의 reference count가 0이 되면 객체의 메모리 할당이 해제 된다.
- 변수에 객체 할당
- list에 추가하거나 인스턴스에서 속성으로 추가하는 등의 자료구조에 객체 추가.
- 겍체를 함수의 인수로 전달
예시
import sys
a = 'hello'
# 레퍼런스 카운트 +1
sys.getrefcount(a)
# sys가 참조하니 레퍼런스 카운트 +1
# return 2
print('hello')
# sys.getrefcount() 종료 -1
# 프로그램이 동작이 끝났으니 제거 -1
Generational Garbage Collection(세대별 가비지 컬렉션)
만약 서로 아래와 같이 서로 참조 하고 있다면 어떻게 될까?
a.count = b.count
b.count = a.count
마지막 상태에서 서로를 참조하기 때문에 래퍼런스 카운터가 0을 도달할수 없는 쓰레기 메모리가 되는 것이다.
이것을 해결하기 위해 나온 것이 가비지 컬렉션이다.
가비지 컬랙션 개념
GC는 내부적으로 세대와 임계값을 통해 가비지 컬렉션 주기와 객체를 관리한다. 세대는 총 0세대, 1세대, 2세대가 있으며 최근에 생성된 객체는 0세대로 들어가고 오래된 객체는 2세대로 이동한다. 가비지 컬렉터는 0세대에 가까울수록 더자주 가비지 컬렉션을 하도록 되어 있다.
주기는 threshold와 관련 있는데 gc.get_threshold()로 확인해 볼 수 있다.
>>> gc.get_threshold()
(700, 10, 10)
threshod값을 초과하면 가비지 컬렉션이 수행된다 이 값은 변경 될 수 없다.
0세대이면 700번 참조되면 가비지 컬렉션이 일어나고 1세대는 한번당 700번일어나야 +1 되므로 7000번만에 한번
2세대는 70000번만에 일어난다는 뜻이다.
예를 들어 자동차라는 객체를 만들면 0세대에 넣는다.자동차는 동력기라는 객체를 참조하는데 이전에 동력기라는 객체가 0세대의 임계값(threshod)보다 많이 참조되었다면 가비지 컬렉션을 수행한다. 그리고 가비지 컬렉션은 2세대 부터 차근 차근 수행하면서 일어난다.
순환 참조는 어떻게 해결하는가?
가비지 컬렉션은 수행하기전에 순환 참조 탐지 알고리즘을 통하여 특정 세대에서 도달할 수 있는 객체를 찾는데 도달 할수 있는 객체는 세대를 이전 시키고 도달할 수 없는 객체는 메모리에서 해제된다.
- 객체에 gc_refs 필드를 레퍼런스 카운트와 같게 설정한다.
- 각 객체에서 참조하고 있는 다른 컨테이너 객체를 찾고, 참조되는 컨테이너의 gc_refs를 감소시킨다.
- gc_refs가 0이면 그 객체는 컨테이너 집합 내부에서 자기들끼리 참조하고 있다는 뜻이다.
- 그 객체를 unreachable(도달할 수 없는 코드) 하다고 표시한 뒤 메모리에서 해제한다.