메모리 누수 (Memory Leak):
메모리 누수는 프로그램이 사용하지 않는 메모리를 계속해서 점유하고 있어, 시간이 지나면서 사용 가능한 메모리가 줄어드는 현상을 의미합니다.
메모리 누수는 프로그램의 실행 시간이 긴 경우에 심각한 문제를 일으킬 수 있습니다. 프로그램이 계속 실행될수록 사용 가능한 메모리가 감소하게 되며, 결국에는 프로그램이 메모리 부족으로 인해 실패할 수 있습니다.
순환 참조 (Cyclic Reference):
순환 참조는 두 개 이상의 객체가 서로를 참조하는 상황을 의미합니다. 예를 들면, 객체 A가 객체 B를 참조하고, 동시에 객체 B가 객체 A를 참조하는 경우입니다.
순환 참조가 발생하면, 단순한 참조 카운팅 기반의 가비지 컬렉션 메커니즘에서는 이러한 객체들을 가비지로 간주하지 않기 때문에, 이러한 객체들은 메모리에서 해제되지 않게 됩니다.
class Node:
def __init__(self, value):
self.value = value
self.ref = None
# 두 개의 노드 생성
node1 = Node("Node 1")
node2 = Node("Node 2")
# 노드들이 서로를 참조하도록 설정
node1.ref = node2
node2.ref = node1
여기서 node1의 ref 속성은 node2를 참조하고, node2의 ref 속성은 node1를 참조합니다. 이렇게 두 객체가 서로를 참조하는 경우, 순환 참조가 발생하게 됩니다.
단순한 참조 카운트만으로는 이러한 순환 참조를 해결하기 어렵습니다. 왜냐하면 node1과 node2 모두 참조 카운트가 0이 아니기 때문입니다. 그러나 파이썬의 가비지 컬렉션은 순환 참조를 감지하고 처리할 수 있는 메커니즘을 가지고 있습니다. 따라서, CPython에서는 위의 코드에서 발생하는 순환 참조가 가비지 컬렉션에 의해 처리될 수 있습니다.
참조 카운트(reference count)
"참조 카운트(reference count)"는 파이썬의 객체에 대한 참조 횟수를 나타내는 값입니다. 즉, 몇 개의 변수나 다른 객체들이 해당 객체를 참조하고 있는지의 수를 나타냅니다.
객체가 생성될 때, 그 참조 카운트는 1로 시작합니다. 객체에 대한 추가적인 참조가 생길 때마다 참조 카운트는 증가하고, 참조가 사라질 때마다 감소합니다. 참조 카운트가 0이 되면, 해당 객체는 더 이상 프로그램에서 사용되지 않는 것으로 간주되며 메모리에서 해제될 수 있습니다.
파이썬의 sys 모듈의 getrefcount 함수를 사용하면 특정 객체의 참조 카운트를 조회할 수 있습니다.
import sys
obj = []
print(sys.getrefcount(obj)) # 2를 출력. `obj` 변수와 `getrefcount` 함수의 인자로서의 참조.
another_reference = obj
print(sys.getrefcount(obj)) # 3을 출력. 기존의 두 참조에 추가로 `another_reference`로 인한 참조가 생겼음.
del another_reference
print(sys.getrefcount(obj)) # 다시 2를 출력. `another_reference` 참조가 삭제되었으므로.
참고로, getrefcount는 인자로 받은 객체에 대한 참조도 포함되므로 실제 참조 카운트보다 1 더 큰 값을 반환합니다.
x = [] # x라는 변수가 빈 리스트를 참조합니다. 여기서 1
print(sys.getrefcount(x)) # 이 순간, 실제로는 x 하나만이 리스트를 참조하지만, getrefcount 함수의 인자로 전달되는 참조 때문에 1이 더해진 값을 반환합니다.
CPython 구현에서 참조 카운팅은 메모리 관리의 주요 메커니즘이며, 참조 카운트가 0이 되면 해당 객체의 메모리는 즉시 해제됩니다. 그러나, 순환 참조와 같은 경우에는 참조 카운트만으로는 메모리 누수 문제가 발생할 수 있기 때문에, 추가적인 가비지 컬렉션 메커니즘도 함께 사용됩니다.