[Python] GC ( Garbage collector)

김지환·2022년 8월 13일
0

파이썬은 참조 기반의 가비지 컬렉터로 메모리 관리를 합니다.

이 때 레퍼런스 카운팅, 세대관리 두 가지 방식의 조화로 관리됩니다.

레퍼런스 카운팅, 세대관리

파이썬의 객체는 참조를 당할 때마다 reference count 값이 올라가게 됩니다. 코드로 확인을 해보면 아래와 같습니다.
직접 코드를 돌려보면서 동작을 보면 이해가 쉽습니다.

import gc
import sys


class Human:
    _id: int = None
    _name: str = None
    items = list()

    def __init__(self, id: int, name: str):
        self._id = id
        self._name = name

    def add_item(self, stuff):
        self.items.append(stuff)

    def clear_items(self):
        self.items.clear()


class Stuff:
    _name: str = None
    owner = list()

    def __init__(self, name):
        self._name = name

    def add_owner(self, human: Human):
        self.owner.append(human)

    def clear_owner(self):
        self.owner.clear()


if __name__ == "__main__":
    print(gc.get_count())  # 현재 0세대, 1세대, 2세대 의 참조 카운트를 return 
    human_hong = Human(940101, "홍길동")
    ball = Stuff("공")

    print(gc.get_count())

    print(human_hong) # 해당 인스턴스의 주소
    print(ball) # 해당 인스턴스의 주소

    print(f"human_hong 객체의 Referce count: {sys.getrefcount(human_hong) - 1}")  # getrefcount 에 의해서 참조 카운트가 1 늘어나기 때문에 1감소를 해줘서 결과물을 직관적으로 볼 수 있게함.
    print(f"ball 객체의 Referce count: {sys.getrefcount(ball) - 1}")


    human_hong.add_item(ball)
    ball.add_owner(human_hong)

    print(f"human_hong 객체의 Referce count: {sys.getrefcount(human_hong) - 1}")
    print(f"ball 객체의 Referce count: {sys.getrefcount(ball) - 1}")

    human_hong.clear_items()  # clear 를 통해 refcount 를 1 감소
    ball.clear_owner()

    print(f"human_hong 객체의 Referce count: {sys.getrefcount(human_hong) - 1}")
    print(f"ball 객체의 Referce count: {sys.getrefcount(ball) - 1}")

    del human_hong # del 로 인해 count 가 0 으로 됨
    del ball # del 로 인해 count 가 0 으로 됨

실행결과

(122, 1, 1)
(124, 1, 1)
<__main__.Human object at 0x7fa376d9acd0>
<__main__.Stuff object at 0x7fa376d9ae20>
human_hong 객체의 Referce count: 1
ball 객체의 Referce count: 1
human_hong 객체의 Referce count: 2
ball 객체의 Referce count: 2
human_hong 객체의 Referce count: 1
ball 객체의 Referce count: 1

레퍼런스 카운팅

첫 프로세스를 돌렸을 때 get_count를 통해 각 세대의 참조카운트를 보게 되면 default로 형성되는 값들이 있기 때문에 (0,0,0) 으로 나오지 않는다.

이후 human_hong, ball 인스턴스를 만들게 되면 0세대 카운트가 2 증가하게 된다.

그 후 각각의 인스턴스에 있는 list member 변수에 서로를 추가한다.
서로에게 저장이 되면서 서로의 주소 정보를 가리키는 변수들이 각각 1개씩 늘어나게 된것이다. 따라서 카운트를 확인해보면 1씩 증가함을 볼 수 있다.

다시 각각의 멤버 list를 clear 하여 refcount를 줄이면 다시 1로 감소함을 볼 수 있고 마지막 del 을 통해 모든 refcount를 줄여주면 메모리가 해제되게된다.

메모리 해제는 가비지 컬렉션이 돌아갈 때 이루어지고 가비지 컬렉션은 세대주기 별로 동작하게 됩니다.

세대주기

파이썬 객체가 등록될 때마다 가비지 컬렉터에 내부적으로 등록이 되게 됩니다. 이렇게 초기 등록된 객체는 0세대로 구분지어지게 되고 객체가 추가됨에 따라 점차 1세대, 2세대로 넘어가게 됩니다. 각 세대로 넘어가게 되는 기준은 가비지 컬렉터 내부에 정의 되어 있는데 default 값은 다음과 같습니다.

if __name__ == "__main__":
    print(gc.get_threshold())  # 0세대, 1세대, 2세대 Threshold
    
(700, 10, 10) # 0세대, 1세대, 2세대

0세대의 객체가 700 을 넘게 되면 해당 값이 0으로 변경되고 각 객체별로 분류가 되게 되는데 도달 가능한 객체, 도달할 수 없는 객체로 나뉘게 됩니다.

그래서 1세대 카운트가 전부 차서 2세대로 넘어가기 위해서는 총 700 * 10 = 7000 번의 카운팅이 이뤄져야함을 알 수 있습니다.

이후 도달 가능한 객체들은 다음 세대 리스트와 병합되고, 도달할 수 없는 객체들은 메모리에서 제거됩니다.

다른 객체에 참조되고 있는 수 A와 현재 레퍼런스 카운트 B를 빼서 B - A > 0 일 경우 도달 가능한 객체가 되고, 0 일 때 도달할 수 없는 객체(unreachable)로 분류합니다.

Reference

https://blog.winterjung.dev/2018/02/18/python-gc
https://docs.python.org/3/glossary.html
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

profile
Developer

0개의 댓글