RSS가 줄지 않을 때는 누수인지 할당자 보유인지 먼저 가른다
타입별 객체 수가 안정적이면 누수가 아니라 할당자가 빈 메모리를 들고 있는 것이다. 객체 수 → mallinfo2 → malloc_trim 순서로 나눠 본다.
파이썬 서버의 RSS가 올라가기만 하고 내려오지 않는다. 누수라고 단정하고 코드를 뒤지기 전에, 정말 객체가 쌓이는 것인지부터 가른다. 객체는 다 풀렸는데 할당자가 빈 메모리를 OS에 돌려주지 않는 경우가 많다.
순서대로 본다.
- 타입별 객체 수:
gc.get_objects()를 타입별로 세어 시간에 따라 비교한다. 어떤 타입이 계속 늘면 누수다.gc.get_referrers()로 그 객체를 붙잡는 쪽을 찾는다. - 힙의 빈 공간: 객체 수가 안정적이면
mallinfo2()로 힙 크기 대비 빈 공간(fordblks)을 본다. 많이 비어 있으면 할당자 보유다. - 확정:
malloc_trim(0)을 한 번 불러 RSS가 내려가는지 본다. 내려가면 확정이다. - 영역 나누기:
/proc/<pid>/smaps로 힙·arena·mmap 가운데 어디가 큰지 나눈다.
import collections
import ctypes
import gc
def count_types(top: int = 15):
return collections.Counter(type(o).__name__ for o in gc.get_objects()).most_common(top)
class MallInfo2(ctypes.Structure):
_fields_ = [(name, ctypes.c_size_t) for name in (
"arena", "ordblks", "smblks", "hblks", "hblkhd",
"usmblks", "fsmblks", "uordblks", "fordblks", "keepcost",
)]
libc = ctypes.CDLL("libc.so.6")
libc.mallinfo2.restype = MallInfo2
info = libc.mallinfo2()
print(f"heap {info.arena >> 20}MB, free {info.fordblks >> 20}MB")
libc.malloc_trim(0)
주의: 진단 코드 자체가 객체를 만들고 메모리를 바꾼다. 같은 측정을 여러 번 되풀이해 비교하고, 한가한 프로세스에서 기준선을 먼저 잡는다.