본문 바로가기
카테고리 없음

메모리 프래그먼트 — 누수와 속도 저하를 만드는 단편화 원리

by pc-knowledge 2026. 1. 4.
반응형

메모리 때문에 프로그램이 점점 느려지거나, 분명 남은 메모리는 있어 보이는데도 할당이 실패하는 경험… 한 번쯤 있으셨죠.
그럴 때 자주 등장하는 단어가 바로 메모리 프래그먼트(단편화)예요.

오늘은 “왜 단편화가 생기는지”, “누수처럼 보이는 이유는 뭔지”, 그리고 “실무에서 어떤 식으로 해결하는지”를 차근차근 풀어볼게요.
읽다가 떠오르는 상황이 있으면 댓글로 여러분 케이스도 공유해 주세요!

메모리 단편화의 기본 개념과 종류

단편화(프래그먼테이션)는 말 그대로 메모리 공간이 조각조각 나뉘어 효율이 떨어지는 현상이에요.
재미있는 건, “총량”이 부족한 게 아니라 “모양”이 안 맞아서 문제가 생긴다는 점이에요.

예를 들어 빈 방(남은 메모리)이 여러 개 있어도, 새로 들어오는 큰 가구(큰 연속 블록)가 들어갈 ‘연속된’ 공간이 없으면 배치가 안 되는 느낌이죠.
그래서 단편화는 종종 “메모리 남아 있는데 왜 터지지?” 같은 상황을 만들어요.

내부 단편화 vs 외부 단편화

구분 무슨 뜻인가요? 주로 생기는 상황 증상
내부 단편화 할당된 블록 ‘안’에 남는 자투리 공간 고정 크기 블록, 정렬(align)로 인한 패딩 사용량 대비 RSS가 커지고, 효율이 낮아짐
외부 단편화 빈 공간이 ‘바깥’에서 조각나 연속 공간이 부족 가변 크기 할당/해제가 섞여 자주 반복될 때 큰 할당 실패, 페이지 증가, 성능 흔들림

핵심 포인트
단편화는 “메모리가 부족해서”가 아니라 “연속으로 쓸 수 있는 형태가 부족해서” 생기는 문제가 많아요.
그래서 단순히 사용량만 보면 원인을 놓치기 쉬워요.

그리고 단편화는 운영체제 수준(페이지/가상메모리), 런타임(GC), 네이티브 힙(malloc 계열) 등 다양한 층에서 발생할 수 있어요.
어디서 단편화가 생기는지 먼저 구분해두면, 뒤에서 소개할 진단/해결이 훨씬 쉬워집니다.

단편화가 누수처럼 보이는 이유

단편화가 무서운 이유 중 하나는, 겉으로 보면 메모리 누수처럼 보일 수 있다는 점이에요.
분명 객체는 해제했는데, 프로세스 RSS는 잘 안 줄고, 시간이 지날수록 더 큰 메모리가 필요해 보이기도 하죠.

하지만 “해제 자체”가 실패한 게 아니라, 해제된 공간이 조각나거나 재사용이 어려워서 메모리가 계속 커지는 것처럼 보이는 경우가 많아요.

관측 지표가 누수를 오해하게 만드는 포인트

관측 누수처럼 보이는 이유 단편화 관점의 설명
RSS가 안 줄어듦 해제를 했는데도 수치가 유지 힙 내부에 “빈 구멍”이 남아도 OS로 반환되지 않을 수 있음
할당 실패/스파이크 남은 메모리가 있어 보여도 실패 연속 블록이 부족하거나, 큰 청크로 확장해야 해서 급증
GC/컴팩션 빈도 증가 시간이 갈수록 더 자주 멈춤 파편화로 재배치/정리 비용이 커지고, 메모리 지역성이 악화

상황 예시
1) 요청 처리 중 작은 버퍼를 많이 만들었다가 해제함
2) 중간중간 크기가 다른 객체도 섞여서 할당/해제됨
3) 결과적으로 힙이 “빈 구멍 + 큰 덩어리 부족” 상태가 됨

이때 대형 버퍼가 필요해지면, 남은 메모리가 있어도 새 영역을 더 확보해야 해서 RSS가 계속 커지는 것처럼 보여요.

그래서 누수로 의심될 때는 “객체가 진짜로 남아 있는지(레퍼런스/루트 유지)”와 “힙 구조가 재사용 가능한 형태인지(단편화)”를 분리해서 봐야 해요.
누수는 ‘해제가 안 됨’, 단편화는 ‘해제는 됐는데 모양이 나쁨’이라는 감각으로 구분해두면 훨씬 명확해집니다.

속도 저하가 생기는 메커니즘

단편화는 메모리 “용량” 문제만이 아니라, 속도 저하를 아주 집요하게 만들어요.
특히 트래픽이 몰릴 때만 느려졌다가, 한참 뒤에 또 괜찮아지는 식으로 “예측 불가한 성능 흔들림”을 만들기도 합니다.

왜 이런 일이 생길까요? 핵심은 할당/해제의 복잡도 증가, 그리고 메모리 지역성 악화예요.

단편화가 느려지는 대표 이유

체크 포인트
1) 할당 탐색 비용 증가
작은 빈 공간이 많아질수록 “딱 맞는 자리”를 찾는 시간이 늘어나요. 어떤 할당기는 여러 free list를 뒤지거나, 병합(coalescing)을 자주 시도하게 돼요.

2) 페이지/캐시 지역성 악화
데이터가 한 덩어리로 붙어 있으면 CPU 캐시가 유리한데, 파편화가 심하면 같은 작업도 멀리 떨어진 메모리를 여기저기 접근하게 돼요.
결국 캐시 미스가 늘고, 체감 성능이 뚝 떨어질 수 있어요.

3) 페이지 폴트/스왑 위험 증가
단편화로 인해 실제로는 사용량이 비슷해도, 더 많은 페이지를 잡고 있게 되는 경우가 있어요. 이때 시스템 메모리 압박이 오면 페이지 폴트가 증가하고 최악에는 스왑까지 연결됩니다.

주의
“GC가 있는 언어니까 단편화 신경 안 써도 된다”는 오해가 자주 있어요.
실제로는 런타임 정책(세대별 힙, 대형 객체 영역, 컴팩션 빈도)에 따라 단편화가 성능에 크게 영향을 줄 수 있습니다.

정리하자면, 단편화는 “메모리를 덜 쓰면 해결” 같은 단순한 문제가 아니에요.
할당 패턴이 바뀌지 않으면, 사용량이 줄어도 여전히 조각난 상태로 남아 성능을 갉아먹을 수 있습니다.
그래서 다음 STEP에서 “어떻게 진단할지”가 정말 중요해져요.

진단 방법: 지표와 관찰 포인트

단편화는 “한 번에 딱 찍어” 확인하기보다, 여러 단서를 모아 추론하는 경우가 많아요.
그래서 저는 보통 증상 → 지표 → 재현/확인 순서로 봅니다.

특히 “누수인지 단편화인지”를 가르는 핵심은, 실제로 살아있는 객체가 늘어나는지, 아니면 할당 형태가 나빠지는지를 분리해서 보는 거예요.

관찰 포인트 단편화 신호 함께 보면 좋은 것
큰 할당의 실패/지연 특정 크기 이상에서만 문제가 튐 요청 유형별 객체 크기 분포, 버퍼/배열 크기
RSS 증가 vs 실제 사용량 사용량은 큰 변화 없는데 RSS가 계단식 증가 힙 내 free 공간, OS로 반환되는지 여부
지연 시간 분포 p95/p99만 악화(꼬리 지연) 할당/GC 이벤트 타임라인, 스톨 구간
진단할 때 자주 쓰는 질문(접기/펼치기)

1) 같은 작업을 반복했을 때 메모리 패턴이 누적되는가?
반복 후에도 “살아있는 객체 수”가 비슷한데 RSS만 커지면 단편화를 의심해볼 수 있어요.

2) 특정 크기에서만 문제가 생기는가?
예를 들어 1~2MB 이상의 큰 버퍼에서만 실패가 늘면 외부 단편화 가능성이 올라가요.

3) 성능 저하가 꼬리 지연 형태로 나타나는가?
평균은 괜찮은데 p99가 튄다면, 특정 타이밍에만 비싼 정리/확장이 일어날 수 있어요.

TIP
“메모리 총량”만 보지 말고, 할당 크기 분포요청 타입별 메모리 패턴을 같이 기록해두면 원인 찾기가 훨씬 빨라져요.

해결 전략: 설계/구현 레벨의 대응

단편화는 “한 방에 정리”보다, 할당 패턴을 바꾸는 방향이 가장 효과적인 경우가 많아요.
즉, 메모리를 적게 쓰는 것보다 “비슷한 크기를 비슷한 생명주기로” 쓰게 만드는 게 핵심이에요.

아래 전략들은 언어/런타임이 달라도 기본 철학이 비슷합니다. 지금 사용하는 환경에 맞춰 가져다 쓰면 돼요.

실무에서 효과가 큰 대응책

전략 어떤 문제를 줄이나요? 적용 힌트
오브젝트/버퍼 풀링 가변 할당/해제 반복을 줄여 외부 단편화 완화 “자주 쓰는 크기” 위주로 풀을 만들고 상한선을 둠
크기 클래스 정규화 비슷한 크기끼리 모여 재사용성이 좋아짐 예: 3.1KB 대신 4KB로 올림(내부 단편화와 교환)
대형 객체 분리 큰 블록이 작은 객체 사이를 깨뜨리는 현상 감소 큰 배열/버퍼는 별도 영역(대형 객체 영역, mmap 등) 고려
생명주기 분리 짧게 사는 것과 길게 사는 것을 섞지 않음 요청 단위 임시 객체는 한 곳에 모아 한 번에 정리

핵심 포인트
단편화 해결은 “정리”가 아니라 “형태를 예쁘게 유지”하는 싸움이에요.
그래서 할당 크기, 할당 빈도, 생명주기 세 가지를 먼저 안정화시키는 게 가장 빠른 길입니다.

물론 환경에 따라 런타임/할당기 옵션(컴팩션 정책, arena/heap 동작, huge page 등)도 도움이 될 수 있어요.
다만 옵션은 “마지막 한 끗”에 가까운 경우가 많아서, 먼저 설계/패턴을 다듬고 난 뒤 적용하는 걸 추천드려요.

FAQ: 자주 헷갈리는 포인트 정리

1) 메모리 사용량이 낮은데도 OOM이 날 수 있나요?

네, 가능합니다.
총 사용량이 낮아도 “연속된 큰 공간”이 부족하면 큰 할당이 실패할 수 있어요.
특히 외부 단편화가 심하면 “빈 공간은 있는데 들어갈 자리가 없는” 상황이 생깁니다.

2) RSS가 줄지 않으면 무조건 누수인가요?

무조건은 아니에요.
해제된 공간이 힙 내부에 남아도 OS로 바로 반환되지 않을 수 있고, 단편화 때문에 재사용이 비효율적이면 RSS가 잘 안 내려갈 수 있어요.
“살아있는 객체가 정말 늘었는지”를 먼저 확인해보는 게 좋아요.

3) 풀링을 하면 항상 단편화가 줄어드나요?

대체로 도움이 되지만, 무조건은 아니에요.
풀의 크기/상한선이 없으면 오히려 메모리를 과하게 잡아두게 될 수 있어요.
“자주 쓰는 크기만”, “상한선을 두고”, “관측 지표로 조절”이 핵심입니다.

4) 내부 단편화는 그냥 감수해야 하나요?

어느 정도는 감수하는 게 맞는 경우가 많아요.
대신 내부 단편화를 조금 허용하더라도, 크기 클래스를 정규화해서 외부 단편화를 줄이면 전체 효율이 좋아지는 경우가 있어요.
“내부 단편화 vs 외부 단편화”는 트레이드오프로 보는 게 현실적입니다.

5) 단편화는 왜 꼬리 지연(p99)을 악화시키나요?

특정 순간에만 비싼 일이 터지기 때문이에요.
예를 들어 큰 공간을 찾느라 탐색/병합이 길어지거나, 힙 확장/정리 같은 이벤트가 발생하면 그 요청만 유독 느려질 수 있어요.
그래서 평균은 괜찮아도 p99가 흔들리는 패턴이 자주 나옵니다.

6) 가장 빠르게 해볼 수 있는 1순위 개선은 뭐가 좋을까요?

“할당 크기 분포”를 먼저 고정시키는 걸 추천드려요.
자주 쓰는 버퍼 크기를 몇 개로 정해두고(정규화), 임시 객체는 요청 단위로 묶어 정리되게 하면 효과가 빠르게 보이는 편이에요.
가능하면 풀링은 상한선과 함께 도입해보세요.

댓글 질문 환영
여러분이 겪은 “메모리는 남았는데 실패했던 순간”이 있었다면, 어떤 할당이 문제였는지 함께 이야기해봐요.
케이스만 들어도 원인 방향이 꽤 빨리 잡히는 경우가 많습니다.

마무리 인삿말

단편화는 “메모리가 부족하다”는 단순한 결론으로 가기 전에 꼭 한 번 의심해봐야 하는 주제예요.
해제는 잘 되고 있는데도 모양이 망가지면, 누수처럼 보이기도 하고 성능이 들쭉날쭉해지기도 하거든요.

오늘 글이 여러분 환경에서 문제를 좁히는 데 작은 힌트가 되었으면 좋겠습니다.
지금 겪는 증상(느려지는 타이밍, 특정 요청, 할당 크기)을 댓글로 남겨주시면, 함께 원인 방향을 더 구체적으로 잡아볼게요.

관련된 사이트 링크

아래 링크들은 단편화/메모리 관리 개념을 더 깊게 보고 싶을 때 도움이 되는 자료들이에요.
읽다가 막히는 부분이 있으면, 링크 한두 개만 따라가도 감이 많이 잡힙니다.

  1. Microsoft Learn - 메모리/성능 개념
    https://learn.microsoft.com/운영체제/성능 진단 관련 공식 문서가 잘 정리되어 있어요.
  2. LLVM - Sanitizers(메모리 관련 진단)
    https://clang.llvm.org/docs/네이티브 환경에서 메모리 오류/누수 진단에 참고하기 좋아요.
  3. Linux man-pages (malloc, mmap 등)
    https://man7.org/linux/man-pages/힙/가상메모리 동작을 정확히 이해할 때 가장 믿을 만한 1차 자료입니다.
  4. OpenJDK - HotSpot/GC 관련 문서
    https://openjdk.org/GC가 있는 환경에서 단편화/컴팩션 이해에 도움이 돼요.

태그 정리

메모리단편화,프래그먼테이션,메모리누수,힙메모리,할당기,GC,성능최적화,시스템프로그래밍,캐시지역성,OOM

반응형