익숙하지 않은 파이썬으로 알고리즘 문제를 풀다 보니(알고리즘 주간에는 필수적으로 Python으로 문제를 풀어야한다.) 메모리 초과가 뜨는 일이 시간 초과 만큼 굉장히 빈번하게 일어난다.
어떻게 하면 메모리 초과를 없애고 줄일 수 있을까를 생각해보다가 GC까지 오게 되었다.
가비지 컬렉션이란?
메모리 관리 기법 중 하나로 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역을 해제하는 기능이다. 즉, 동적 할당된 메모리 영역 가운데 어떤 변수도 가리키지 않는 메모리 영역을 탐지하여 자동으로 해제하는 기법이다.
쉽게 말하면 메모리에 굳이 남아있을 필요 없는 쓰레기들을 없애 메모리 공간을 깨끗하게 만들어주는 것이라고 생각하면 된다.
원래 C나 C++의 경우에는 malloc()을 통해 동적 할당한 메모리 영역을 free()를 통해 해제하여 직접 메모리 관리를 해줘야하지만, Java나 Python 같은 언어는 위와 같은 함수 없이, 자동으로 할당된 메모리 영역을 해제해주는 GC가 구현되어 사용된다고 한다.(지금은 C나 C++에도 GC 관련 함수가 있다고 한다.)
GC가 왜 필요한가?
1. 필요없는 메모리를 해제해주지 않는다면 메모리 누수라 불리는 memory leak이 발생한다. 메모리 누수가 발생하면 시스템의 성능 저하나 응용 프로그램의 불안정성을 초래할 수 있다.
2. 사용 중이던 메모리를 해제해버리면 프로그램이 중단되고 데이터가 손실될 수 있다.
사용자가 직접 메모리를 관리하는 경우, 실수로라도 돌아가고 있는 프로그램이 사용하는 메모리를 해제해버리면 말 그대로 프로그램이 터져버릴 수도 있다.
이와 같이 사용자가 직접 메모리를 관리해주는 경우, 실수로 인하여 프로그램에 큰 영향을 끼칠 수 있기 때문에 GC를 사용하는 것이다.
그 외에도 2번으로 인해 발생하는 문제인 유효하지 않은 포인터 접근과 이중 해제 같은 것을 방지하기 위해서라도 GC가 필요하긴 하다.
- 유효하지 않은 포인터 접근 : 이미 할당이 해제된 메모리에 접근하는 버그
- 이중 해제 : 이미 할당이 해제된 메모리를 또 해제하는 오류 (ex : C언어에서 free()를 해주고 또 다시 free()를 해주는 경우)
GC는 어떻게 동작하는가?
GC는 다양한 알고리즘과 전략을 사용해 메모리를 관리하는데 파이썬에서는 참조 카운트(reference counting)와 세대별 가비지 컬렉션(generational garbage collection)을 사용하여 가비지 컬렉션을 수행한다.
참조 카운트(reference counting)
모든 객체는 참조 당할 때 레퍼런스 카운터를 증가시키고 참조가 없어질 때 카운터를 감소시킨다. 이 카운터가 0이 되면 객체를 메모리에서 해제한다. 어떤 객체의 레퍼런스 카운트를 보고 싶다면 sys.getrefcount()를 통해 확인이 가능하다.
순환 참조
여기서 문제가 생길 수도 있다. 아래와 같이 객체가 자기 자신을 참조하는 순환 참조가 발생하는 경우 객체의 참조 횟수가 1이지만 객체에 대해 접근이 불가능하여 레퍼런스 카운팅 방식으로는 메모리에서 해제될 수 없게 된다.
자기 자신을 참조하는 것이 아닌, 서로를 참조하는 경우도 발생한다.
이 상황에서 0x60.x 와 0xa8.x가 서로를 참조하고 있기 때문에 레퍼런스 카운트는 둘 다 1이지만 도달할 수 없는 쓰레기가 되어버린다.
(코드 출처 : https://dw3232.tistory.com/71)
세대별 가비지 컬렉션(generational garbage collection)
위와 같이 순환 참조 문제가 생기면 파이썬에서는 세대별 가비지 컬렉션으로 해결한다.
파이썬의 가비지 컬렉터는 내부적으로 generation(세대)과 threshold(임계값)로 가비지 컬렉션 주기와 객체를 관리한다.
1) 세대(generation)
먼저 세대에 대해 설명하겠다.
파이썬에서 세대는 0~2세대로 구분되고, 최근 생성된 객체는 0세대(young)에 들어가고 오래된 객체일수록 1세대와 2세대(old)로 이동한다.
파이썬에서는 0세대일수록 더 자주 가비지 컬렉션을 하도록 설계되어 있다. 만약 0세대에서 수명이 오래되면 1세대로 이동되는데, 1세대에서는 mark-and-sweep 알고리즘을 사용하여 가비지 컬렉션을 수행한다.
============================================================================================
Mark-and-sweep이란?
가비지 컬렉터에는 GC Root라 불리는 가비지 컬렉션의 root가 있는데, 이 root를 통해 참조하는 모든 오브젝트, 그 오브젝트가 참조하는 오브젝트들을 탐색해나가며 mark한다.
그 후, mark되지 않은 객체들을 모두 free() 하는 형식으로 없애는(sweep) 방식이 mark-and-sweep이다.
============================================================================================
이를 통해 0세대에서 순환 참조가 발생해도 1세대로 넘어갈때 메모리 누수를 방지하게 된다.
2) 임계값(threshold)
각 세대마다 가비지 콜렉터 모듈에는 임계값 개수가 있다.
객체 수가 해당 임계값을 초과하면 가비지 콜렉션이 콜렉션 프로세스를 추가한다. 이때 이 프로세스에서 살아남은 객체는 다른 세대로 옮겨지게 된다.(자바의 age와 비슷한 개념)
파이썬에서는 세대에서 가비지 컬렉터의 동작을 변경할 수 있다.
위에서 나온 garbage collection process를 trigger 하기 위한 임계값 변경, garbage collection process를 수동으로 trigger 하거나, garbage collection process를 모두 비활성화할 수 있다.
그 외 언어
이 부분은 추가 예정입니다...
'언어(C, C++, C#)' 카테고리의 다른 글
[C#] this (2) | 2024.11.05 |
---|---|
[C#] object와 var (0) | 2024.11.04 |
[C#] C#의 Collection (0) | 2024.11.04 |
[C++] C++의 STL (0) | 2024.11.03 |
C# vs C++ (2) | 2024.11.02 |