메모리 단편화가 발생하는 이유와 내부·외부 단편화 관리
메모리 단편화의 내부·외부 단편화 차이, 고정·가변 분할 특성, 페이징과 압축을 통한 관리 방법을 정리합니다.
2026-08-14 · 최초 발행 2025-12-28
남은 메모리와 실제 할당 가능 공간은 다르다
메모리가 충분히 남아 있어도 새 프로세스를 적재하지 못할 수 있다. 자유 공간이 작은 조각으로 흩어져 큰 연속 블록을 만들지 못하는 경우다. 반대로 프로세스에 메모리를 할당했더라도, 그 영역 안에는 실제 사용하지 않는 공간이 남을 수 있다.
이처럼 주기억장치에서 프로그램 실행에 쓰이지 못하고 낭비되는 공간을 단편화(Fragmentation)라고 한다. RAM의 메모리 할당 영역에서 발생하며, 메모리 활용도와 시스템 성능을 떨어뜨린다. 낭비가 할당 영역 안에 있느냐, 자유 영역 사이에 있느냐에 따라 내부 단편화와 외부 단편화로 나뉜다.
단편화는 두 가지 모습으로 메모리 낭비를 만든다. 자유 메모리의 총량은 충분하지만 연속적인 큰 블록이 없어 적재에 실패할 수 있고, 이미 할당한 공간 가운데 일부만 사용되어 나머지가 그대로 남을 수도 있다.
할당된 블록 안에서 남는 내부 단편화
내부 단편화(Internal Fragmentation)는 프로세스에 배정된 메모리 영역 내부에서 발생하는 낭비다. 할당 단위가 실제 요구량보다 크면 차이가 남는다.
내부 단편화 크기 = 할당된 크기(n) - 실제 필요한 크기(m)
조건: n ≥ m
고정 크기 블록을 사용하는 상황에서는 요구량이 블록 크기에 정확히 맞지 않으면 남는 영역이 생긴다.
블록 크기: 1024 bytes (1KB)
프로세스 요구: 900 bytes
할당: 1024 bytes
사용: 900 bytes
내부 단편화: 124 bytes (약 12%)
페이지 단위 할당에서도 마지막 페이지는 완전히 채워지지 않을 수 있다.
페이지 크기: 4096 bytes (4KB)
프로세스 크기: 10,000 bytes
필요 페이지: 3개 (12KB)
실제 사용: 10,000 bytes
내부 단편화: 2,096 bytes (약 21%)
고정 분할에서는 메모리를 미리 정해진 크기로 나누고 각 파티션에 하나의 프로세스를 배정한다. 프로세스 크기와 파티션 크기가 다르면 내부 단편화가 발생한다. 페이징 시스템도 고정 크기 페이지를 사용하므로 마지막 페이지에서 내부 단편화가 주로 나타나며, 페이지 크기가 클수록 단편화가 커진다.
흩어진 자유 공간이 만드는 외부 단편화
외부 단편화(External Fragmentation)는 할당되지 않은 자유 메모리가 작은 조각으로 나뉜 상태다. 자유 공간의 합은 충분해도 필요한 크기의 연속 영역이 없으면 프로세스를 적재할 수 없다.
∑(자유 공간 조각들) ≥ 프로세스 크기
그러나 max(자유 공간 조각) < 프로세스 크기
다음 상태에서는 총 자유 공간이 250MB지만, 새 프로세스가 요구하는 200MB를 담을 연속 공간은 없다.
총 메모리: 1000 MB
할당 상태:
[프로세스A: 200MB] [자유: 50MB] [프로세스B: 300MB]
[자유: 100MB] [프로세스C: 250MB] [자유: 100MB]
총 자유 공간: 250MB
새 프로세스 요구: 200MB
연속 공간 최대: 100MB
결과: 할당 실패 (외부 단편화)
가변 분할은 프로세스 크기에 맞춰 동적으로 공간을 할당한다. 프로세스가 종료될 때 빈 공간이 생기고, 시간이 지날수록 이 공간들이 흩어져 외부 단편화가 심해진다. malloc/free를 반복하는 힙(heap) 영역이나 장시간 실행되는 프로그램에서 주로 확인할 수 있다.
분할 방식이 바꾸는 단편화 양상
고정 분할은 메모리를 고정 크기 파티션으로 미리 나누는 방식이다. 각 파티션에는 하나의 프로세스를 배정하므로 구현과 할당이 단순하고 빠르다.
프로세스가 파티션보다 작으면 내부 단편화가 생긴다. 프로세스가 어떤 파티션에도 맞지 않는 경우에는 외부 단편화가 발생한다.
파티션 크기: [100KB, 200KB, 300KB]
프로세스 크기: 150KB
- 100KB 파티션: 작아서 할당 불가 (외부 단편화)
- 200KB 파티션: 할당 가능, 50KB 낭비 (내부 단편화)
- 300KB 파티션: 할당 가능, 150KB 낭비 (내부 단편화)
가변 분할은 프로세스 요구 크기에 맞춰 공간을 동적으로 배정한다. 공간 활용에는 유리하지만 할당 알고리즘이 더 복잡하며, 시간이 지나면 외부 단편화가 생긴다.
프로세스 A: 150KB 할당 → 정확히 150KB 사용
프로세스 B: 200KB 할당 → 정확히 200KB 사용
프로세스 A 종료 → 150KB 빈 공간 생성
새 프로세스 C: 200KB 요구 → 150KB 공간에 할당 불가
| 특성 | 고정 분할 | 가변 분할 |
|---|---|---|
| 파티션 크기 | 고정 | 가변 |
| 할당 방식 | 정적 | 동적 |
| 내부 단편화 | 발생 | 미발생 |
| 외부 단편화 | 발생 | 발생 |
| 구현 복잡도 | 낮음 | 높음 |
| 할당 속도 | 빠름 | 느림 |
| 메모리 효율 | 낮음 | 높음 |
| 사용 사례 | 초기 시스템 | 현대 시스템 |
자유 공간을 합치거나 재배치하는 방법
인접한 자유 공간은 통합(Coalescing)으로 하나의 블록으로 합칠 수 있다. 외부 단편화를 일부 완화하며, 구현이 간단하고 오버헤드가 낮아 실시간으로 적용할 수 있다. 다만 인접하지 않은 조각까지 합칠 수는 없으므로 완전한 해결책은 아니다.
def coalesce_free_blocks(memory):
merged = []
current = memory[0]
for block in memory[1:]:
if current.is_free and block.is_free and current.is_adjacent(block):
# 인접한 자유 블록 통합
current.size += block.size
else:
merged.append(current)
current = block
merged.append(current)
return merged
압축(Compaction)은 프로세스를 메모리 한쪽으로 옮겨 자유 공간을 하나의 연속 블록으로 만드는 방식이다. 외부 단편화를 완전히 제거하고 큰 연속 공간을 확보할 수 있지만, 모든 프로세스를 이동해야 한다. 압축 중 시스템이 정지하고 CPU 시간을 소비하며 주소 재배치도 필요하다.
압축은 일정 시간마다 실행하거나, 단편화 정도가 특정 수준을 넘었을 때, 대형 프로세스 할당이 실패했을 때, 또는 시스템 부하가 낮은 유휴 시간에 실행할 수 있다.
O(n × m)
n: 프로세스 수
m: 평균 프로세스 크기
연속 할당을 피하는 페이징과 세그먼테이션
페이징(Paging)은 프로세스를 고정 크기 페이지로, 물리 메모리를 같은 크기의 프레임으로 나누는 방식이다. 페이지는 연속된 물리 공간에 놓일 필요가 없으며 페이지 테이블이 페이지와 프레임의 대응을 관리한다.
페이징은 연속 공간을 요구하지 않으므로 외부 단편화를 해결하고, 빈 프레임을 모두 활용할 수 있다. 그러나 마지막 페이지에는 내부 단편화가 남고 페이지 테이블 관리와 주소 변환 비용이 발생한다. TLB 미스 시 성능 저하도 고려해야 한다.
평균 내부 단편화 = 페이지 크기 / 2
페이지 크기가 클수록 단편화 증가
| 페이지 크기 | 장점 | 단점 |
|---|---|---|
| 작음 (1KB) | 내부 단편화 감소 | 페이지 테이블 크기 증가 |
| 중간 (4KB) | 균형잡힌 성능 | 일반적 선택 |
| 큼 (2MB) | 페이지 테이블 작음 | 내부 단편화 증가 |
세그먼테이션(Segmentation)은 코드, 데이터, 스택, 힙처럼 의미 있는 논리 단위로 메모리를 나누고, 각 세그먼트에는 가변 크기를 부여한다. 논리 구조를 반영하고 보호와 공유에 유리하며 내부 단편화는 없다. 대신 외부 단편화와 세그먼트 테이블 관리 비용이 따른다.
두 기법을 결합하면 세그먼트의 논리 구조를 유지하면서, 각 세그먼트를 다시 페이지로 나눠 프레임에 매핑할 수 있다.
이 방식은 외부 단편화를 제거하고 유연한 메모리 관리를 제공하지만, 구현이 복잡해지고 이중 주소 변환 및 메모리 테이블 비용이 늘어난다.
운영체제와 프로그램에서의 관리
Linux에서는 Buddy System으로 2의 거듭제곱 크기 블록을 관리하고, Slab Allocator는 객체 크기별 캐시를 사용한다. 페이징은 4KB 기본 페이지를 사용하며 Huge Pages는 2MB/1GB 대형 페이지를 제공한다.
Windows는 Virtual Memory Manager를 사용하며, 페이징은 4KB 페이지, Large Pages는 2MB 페이지를 사용한다. 동적 메모리 압축도 적용한다.
임베디드 시스템에서는 예측 가능한 성능을 위해 고정 분할을 사용하고 동적 할당을 제한하며 압축을 최소화한다.
반복적으로 생성·삭제되는 객체에는 메모리 풀을 적용할 수 있다.
// 고정 크기 객체 풀
struct MemoryPool {
void* blocks;
size_t block_size;
size_t num_blocks;
void* free_list;
};
void* pool_alloc(MemoryPool* pool) {
if (pool->free_list == NULL) return NULL;
void* block = pool->free_list;
pool->free_list = *(void**)block;
return block;
}
메모리 풀은 단편화를 막고 할당과 해제를 빠르게 하며 예측 가능한 성능을 제공한다. 객체 재사용은 자주 생성·삭제되는 객체의 풀링을 통해 단편화를 줄이고, 정렬을 고려한 할당은 캐시 효율을 높이며 단편화를 최소화한다.
단편화 상태를 읽는 지표와 도구
외부 단편화는 최대 자유 블록과 전체 자유 공간의 관계로 살필 수 있다.
외부 단편화 비율 = (최대 자유 블록 크기) / (총 자유 공간 크기)
0에 가까울수록 심각 (작은 조각 다수)
1에 가까울수록 양호 (큰 연속 공간)
내부 단편화와 전체 활용률은 다음 식으로 확인한다.
내부 단편화 비율 = (총 낭비 공간) / (총 할당 공간)
활용률 = (사용 중인 메모리) / (총 메모리)
Linux에서는 버디 시스템, 슬랩 할당자, 프로세스 메모리 맵을 다음 명령으로 확인할 수 있다.
# 메모리 단편화 확인
cat /proc/buddyinfo
# 슬랩 할당자 상태
cat /proc/slabinfo
# 메모리 맵
cat /proc/<pid>/maps
Windows에서는 성능 모니터와 메모리 진단 명령을 사용할 수 있다.
# 성능 모니터
perfmon /res
# 메모리 진단
mdsched
고정 분할에서는 내부와 외부 단편화가 모두 생길 수 있고, 가변 분할에서는 외부 단편화가 문제가 된다. 통합은 인접한 자유 공간을 묶고, 압축은 프로세스를 이동해 외부 단편화를 완전히 제거한다. 현대 운영체제는 주로 페이징으로 외부 단편화를 해결하며, 페이지 크기 선택으로 내부 단편화를 관리한다. 메모리 풀, 버디 시스템, 슬랩 할당자는 이러한 관리 방식을 보완한다.