메모리 누수
Memory Leak
개념
다 쓴 자원이 반환되지 않고 계속 쌓여 시간이 갈수록 프로그램이 무거워지고 결국 멈추게 되는 문제입니다.
등장 배경
프로그램은 데이터를 다룰 때마다 메모리를 빌려 쓰고 다 쓰면 돌려줍니다. 요즘 쓰는 언어에는 더 이상 아무도 쓰지 않는 데이터를 찾아 자동으로 치우는 장치가 들어 있어서, 개발자가 일일이 반납을 적지 않아도 됩니다. 문제는 이 장치의 판단 기준이 "아무도 안 쓴다" 가 아니라 "아무도 가리키지 않는다" 라는 데 있습니다. 실제로는 잊힌 데이터라도 어딘가에서 아직 손가락 하나를 걸쳐 두고 있으면 치워지지 않습니다.
이 차이는 예전 웹에서는 잘 드러나지 않았습니다. 링크를 누를 때마다 화면 전체를 새로 받아 오는 구조에서는 페이지가 바뀔 때마다 쌓인 것이 전부 비워졌고, 몇 분에 한 번씩 청소가 일어나니 조금 새도 티가 나지 않았습니다.
한 페이지 안에서 화면만 갈아 끼우는 SPA 방식이 퍼지면서 사정이 달라졌습니다. 탭을 한 번 열면 화면을 스무 번 옮겨 다녀도 같은 문서가 그대로 살아 있습니다. 페이지덱에서 실제로 이런 문의가 들어왔습니다. 오후만 되면 문서 편집기가 버벅인다는 내용이었는데, 태오가 아침에 열어 몇 번 눌러 본 화면은 멀쩡했습니다. 아침에 열고 하루 종일 켜 두는 사용자의 브라우저에서만 벌어지는 일이었던 겁니다.
작동 방식
쓰지 않게 된 데이터를 누군가 여전히 가리키고 있어서 자동 회수 장치가 치우지 못하고, 그 상태가 반복되면서 쌓입니다.
가리킴이 남는 경로는 대개 네 가지입니다.
- 화면을 닫을 때 이벤트 처리기를 떼지 않은 경우입니다. 창 크기 변화나 스크롤에 반응하도록 붙여 둔 함수는 화면이 사라져도 자동으로 떨어지지 않습니다. 그 함수가 이미 닫힌 화면의 데이터를 붙들고 있으면 화면 하나를 열고 닫을 때마다 한 덩이씩 남습니다.
- 타이머를 멈추지 않은 경우입니다. 3초마다 새 내용을 확인하도록 걸어 둔 반복 실행은 화면을 떠나도 계속 돕니다. 편집기를 열 때마다 하나씩 늘어나면 오후에는 같은 확인이 초당 몇 번씩 일어납니다.
- 전역 보관함이 끝없이 커지는 경우입니다. 한 번 불러온 문서를 다시 부르지 않으려고 저장해 두는 캐싱은 그 자체로는 성능을 위한 기법이지만, 넣기만 하고 빼는 규칙이 없으면 보관함이 세션 내내 자랍니다. 상한과 오래된 항목을 밀어내는 규칙이 있어야 캐시이고, 없으면 누수입니다.
- 사라진 화면 요소를 변수가 계속 붙들고 있는 경우입니다. 목록에서 지운 항목을 나중에 쓰려고 배열에 담아 두면, 화면에서는 사라졌지만 메모리에서는 그대로 남습니다.
쌓이는 모양은 계단식입니다. 한 번 새는 양이 작아 몇 분 써서는 표가 나지 않고, 화면을 옮길 때마다 조금씩 올라갑니다. 어느 선을 넘으면 자동 회수 장치가 더 자주, 더 오래 돌기 시작합니다. 이때부터 사용자는 입력이 한 박자 늦게 반영되는 것을 느낍니다. 끝까지 가면 탭이 통째로 죽습니다.
쓰이는 곳
이 말이 가장 자주 오가는 곳은 사용자가 탭을 닫지 않는 제품입니다. 협업 문서 툴, 채팅, 관제 화면처럼 하루 종일 열어 두는 서비스가 여기 속합니다. 첫 화면이 뜨는 속도를 재는 LCP 같은 지표는 열자마자 측정을 끝내기 때문에 이 문제를 전혀 잡지 못합니다. 조작에 화면이 반응하기까지 걸린 시간을 재는 INP는 오래 켜 둔 탭에서 나빠지지만, 그때는 이미 사용자가 느려졌다고 느낀 뒤입니다.
서버 쪽에서도 같은 이름을 씁니다. 한 번 띄우면 몇 주씩 도는 프로세스가 요청을 처리할 때마다 조금씩 반납을 빠뜨리면, 며칠 뒤 메모리를 다 쓰고 강제 종료됩니다. 자동으로 다시 띄워지니 사용자는 잠깐 오류를 보고 말지만, 원인을 찾지 않으면 며칠마다 같은 인시던트가 반복됩니다. 배포를 자주 하는 팀이 늦게 발견하는 것도 같은 이유입니다. 이틀에 한 번 새 버전을 올리면 프로세스가 그때마다 새로 뜨면서 증상이 지워집니다.
찾아내는 방법은 시간을 놓고 보는 것입니다. 메모리 사용량을 재는 APM 같은 도구에서 그래프가 톱니 모양으로 오르내리는 대신 계속 우상향하면 의심합니다. 브라우저에서는 문제가 되는 동작을 스무 번쯤 반복한 뒤 메모리 상태를 앞뒤로 찍어 비교합니다. 반복 전후로 같은 종류의 데이터가 스무 개씩 늘어나 있으면 그게 새는 지점입니다. 이렇게 밖에서 안을 들여다볼 수 있게 해 두는 관측성이 갖춰져 있느냐가 발견까지 걸리는 시간을 가릅니다.
실무 용례
"메모리 누수 같아요. 재현하려면 탭을 세 시간은 열어 둬야 할 것 같은데요."
몇 분 써 보는 확인으로는 잡히지 않는 종류라고 미리 알리는 말입니다.
"편집기 들어갔다 나오기를 스무 번 반복하고 힙 찍어 봤더니 리스너가 그대로 스무 개 남아 있어요."
새는 지점을 실제로 특정했다는 보고입니다.
"일단 새벽에 프로세스 재시작 걸어 두고, 원인은 따로 볼게요."
증상을 임시로 덮고 근본 원인 조사는 남겨 둔다는 뜻입니다.
페이지덱에서는 오후 버벅임 문의가 열 건쯤 쌓인 뒤에야 이 문제가 잡혔습니다. 처음 몇 건은 사용자 컴퓨터 사양 문제로 분류됐습니다. 지현이 문의 시간대가 전부 오후에 몰려 있는 것을 발견해 태오에게 넘겼고, 태오가 편집기를 스무 번 열고 닫으며 메모리를 재서 원인을 찾았습니다. 화면을 닫을 때 스크롤 처리기를 떼지 않은 코드 한 줄이었습니다.
흔한 오해
재시작하면 없어지니 큰 문제가 아니라고 보는 시각이 가장 흔합니다. 새벽에 프로세스를 다시 띄우는 설정 한 줄로 증상이 사라지는 것을 실제로 보기 때문에 그렇게 정리하기 쉽습니다. 하지만 재시작은 쌓인 것을 비울 뿐 새는 속도를 바꾸지 않습니다. 사용자가 늘어 새는 속도가 두 배가 되면 하루를 버티던 프로세스가 반나절 만에 죽고, 그때는 새벽이 아니라 점심시간에 서비스가 끊깁니다. 덮어 둔 시간이 길수록 원인이 된 코드는 다른 코드에 더 깊이 묻힙니다.
자동으로 메모리를 치워 주는 언어를 쓰니 누수가 없다고 믿는 것도 잦은 오해입니다. 반납을 직접 적지 않아도 되는 것은 맞지만, 앞에서 본 것처럼 자동 회수는 가리키는 것이 하나도 없을 때만 일어납니다. 떼지 않은 처리기 하나, 멈추지 않은 타이머 하나가 그 조건을 계속 깨뜨립니다. 오히려 반납을 직접 적는 언어에서는 빠뜨린 자리가 코드에 드러나는데, 자동 회수에 맡기는 쪽에서는 아무 데도 드러나지 않아 찾기가 더 어렵습니다.
같이 보면 좋은 페이지
마지막 업데이트 2026-09-16