Define Fragmentation and Explain Its Types

Define fragmentation and explain its types

Ask any systems programmer about the most quietly destructive force in memory management, and fragmentation is likely near the top of the list. It doesn’t crash your program outright. It doesn’t throw an obvious error message. Instead, it slowly erodes the usable capacity of memory until, one day, an allocation request that should easily succeed fails — even though there’s technically “enough” free memory in the system. Understanding fragmentation is essential to understanding why memory management is such a deep and enduring topic in operating system design.

What Is Fragmentation?

Fragmentation refers to the inefficient use of memory that occurs when free memory space is broken up into small, scattered, or awkwardly-sized blocks over time, such that it becomes difficult or impossible to satisfy new memory requests — even when the total amount of free memory would otherwise be sufficient.

There are two fundamentally different types of fragmentation: internal fragmentation and external fragmentation. They arise from different mechanisms and are addressed with different solutions, so it’s important to understand them as distinct problems rather than variations of the same thing.

Internal Fragmentation

Internal fragmentation occurs when memory is allocated in fixed-size blocks (such as pages or fixed-size partitions), but the process doesn’t use the entire block — leaving wasted space inside an allocated region that cannot be given to any other process.

Example

Imagine a paging system with a fixed page size of 4KB. A process requests memory for a data structure that only needs 5KB. The system must allocate two full pages (8KB total) to satisfy this request, because pages can’t be split further. That leaves:

Requested: 5 KB
Allocated: 8 KB (2 pages × 4 KB)
Wasted (internal fragmentation): 3 KB

That 3KB sits unused inside memory that’s technically marked as “allocated” to the process — it can’t be reclaimed or given to any other process until the whole page is freed.

Why It Happens

Internal fragmentation is essentially the cost of using fixed-size allocation units. The smaller the fixed unit (e.g., smaller page sizes), the less internal fragmentation on average — but smaller units mean more overhead in bookkeeping (larger page tables, more entries to manage). This is a classic space-time/space-space tradeoff in OS design.

External Fragmentation

External fragmentation occurs when free memory becomes broken into small, non-contiguous blocks scattered between allocated regions, such that even though the total free memory is sufficient for a new request, no single contiguous block is large enough to satisfy it.

Example

Picture a memory pool with allocations and deallocations happening over time:

Initial state (all free):
[                    1000 KB free                     ]

After allocating three processes (300KB, 200KB, 250KB):
[ P1: 300KB ][ P2: 200KB ][ P3: 250KB ][  250KB free  ]

Process P2 finishes and frees its memory:
[ P1: 300KB ][  200KB free  ][ P3: 250KB ][  250KB free  ]

Total free memory = 200 + 250 = 450 KB
But the largest contiguous block = only 250 KB

A new request for 400KB FAILS — even though 450KB total is free!

This is the hallmark of external fragmentation: sufficient aggregate free memory, but no single hole big enough to satisfy the request, because the free space is fragmented between (external to) allocated blocks.

Why It Happens

External fragmentation is primarily a consequence of variable-sized allocation — the exact scenario found in segmentation-based memory management and in dynamic memory allocators (like the C heap allocator, malloc/free) that hand out variably-sized chunks. As blocks are allocated and freed in different orders and different sizes, the free space naturally becomes a patchwork of gaps.

Side-by-Side Comparison

AspectInternal FragmentationExternal Fragmentation
Where waste occursInside an allocated blockBetween allocated blocks
Root causeFixed-size allocation unitsVariable-sized allocation units
Common inPaging systemsSegmentation, dynamic heap allocators
Detectable by processNo — the OS/allocator absorbs the waste silentlyIndirectly — allocation failures despite “enough” free memory
Fixed byChoosing appropriate block/page sizesCompaction, coalescing, best-fit/buddy allocators

Solutions to External Fragmentation

1. Compaction

Compaction physically relocates allocated blocks to consolidate all free memory into one contiguous region. It’s effective but expensive — it requires pausing execution (or careful concurrent handling), updating every pointer/reference to relocated memory, and copying potentially large amounts of data. Some managed-memory runtimes (like the JVM’s garbage collector) perform compaction as part of routine garbage collection cycles.

2. Coalescing (Merging Adjacent Free Blocks)

Whenever a block is freed, the allocator checks whether the blocks immediately before and after it are also free, and if so, merges them into a single larger free block. This is a standard technique in most general-purpose heap allocators (glibc’s malloc, for instance) and helps slow the fragmentation process, though it doesn’t eliminate it entirely, since non-adjacent free blocks still can’t merge.

3. Best-Fit, Worst-Fit, and First-Fit Allocation Strategies

  • First-fit: allocate the first free block large enough — fast, but tends to fragment memory at the start of the pool over time.
  • Best-fit: allocate the smallest free block that’s still large enough — minimizes wasted space per allocation, but tends to leave many tiny, unusable fragments.
  • Worst-fit: allocate the largest available block — leaves a larger remaining fragment that’s more likely to be useful for future requests, though this strategy is less commonly used in practice.

4. Buddy Memory Allocation

The buddy system, used in the Linux kernel’s page allocator, divides memory into power-of-two-sized blocks. When a block is freed, the allocator checks whether its “buddy” (the adjacent block of the same size) is also free, and if so, merges them into a larger block — recursively, up to the full pool size. This provides fast, predictable coalescing at the cost of some internal fragmentation (since allocations are rounded up to the nearest power of two).

5. Paging (Eliminating External Fragmentation Entirely)

The most decisive solution, as discussed extensively in operating systems, is simply avoiding variable-sized allocation altogether. Paging divides both physical memory and process address spaces into fixed-size frames/pages, so any free frame can satisfy any page request — there’s no concept of a “too small hole,” because all holes are the same size. This is precisely why paging displaced segmentation as the primary memory management model in modern operating systems: it trades a small, bounded internal fragmentation cost for the complete elimination of external fragmentation.

Fragmentation Across Platforms

Linux

The Linux kernel’s page allocator uses the buddy system to manage physical page frames, minimizing external fragmentation for kernel memory. For heap memory within a process, glibc’s malloc implementation (based on ptmalloc/dlmalloc lineage) uses a combination of free-list bins by size class and boundary-tag coalescing to control fragmentation. The slab allocator (and its successors, SLUB/SLOB) further reduces internal fragmentation for frequently-allocated, fixed-size kernel objects (like task_struct or inode structures) by pre-carving pages into object-sized chunks.

Windows

The Windows heap manager uses a similar segmented free-list approach with “low fragmentation heap” (LFH) mode specifically designed to reduce fragmentation for applications making many small, similarly-sized allocations — a common pattern in GUI and server applications.

Android and iOS

Both mobile platforms are especially sensitive to fragmentation given constrained RAM. Android’s ART runtime uses a generational, compacting garbage collector partly to combat heap fragmentation over an app’s lifetime. iOS’s Objective-C/Swift runtime similarly benefits from ARC’s deterministic deallocation, which tends to fragment less chaotically than pure tracing GC, though malloc-level heap fragmentation is still a real concern for long-running apps and is monitored via Instruments.

Real-World Impact

  • Long-running server processes (databases, web servers) are the most vulnerable to external fragmentation because they run for extended periods with constantly varying allocation sizes — this is one reason database engines often implement their own custom memory allocators rather than relying purely on the OS/libc allocator.
  • Game engines frequently use custom pool allocators and arena allocators specifically to sidestep general-purpose allocator fragmentation, since predictable frame-time performance is critical.
  • Embedded systems with very limited RAM can fail catastrophically from fragmentation, since there’s no virtual memory/swap to fall back on — a fragmented 64KB heap might fail to allocate a 2KB buffer even with 10KB technically free.

Troubleshooting Fragmentation Issues

  1. Monitor allocation failure patterns — if allocations fail intermittently despite apparently sufficient free memory, suspect external fragmentation.
  2. Use heap profiling tools — Valgrind’s massif tool, Windows’ Heap Debugging tools, or custom allocator statistics can visualize fragmentation over time.
  3. Check allocator-specific metrics — glibc exposes mallinfo()/mallinfo2() showing total allocated vs. actually used memory; a large gap suggests fragmentation.
  4. Consider a custom allocator — pool, slab, or arena allocators for objects of predictable size can sidestep general-purpose allocator fragmentation entirely.
  5. Restart long-running processes periodically as a pragmatic (if inelegant) mitigation, especially in production systems where root-causing fragmentation is impractical in the short term.

Best Practices

  1. Prefer fixed-size object pools for frequently allocated/deallocated objects of the same size.
  2. Avoid unnecessary variable-sized allocations in hot paths — batch or pre-allocate where possible.
  3. Choose page/block sizes appropriate to your workload to balance internal fragmentation against page table overhead.
  4. Use allocator profiling tools during load testing, not just functional testing, since fragmentation is a time-and-usage-pattern dependent problem that won’t show up in short test runs.
  5. In systems programming, consider buddy or slab allocators for kernel-level or performance-critical fixed-size allocations.

Summary

Fragmentation is the general problem of memory becoming unusable due to how it’s divided and reused over time, and it comes in two distinct flavors: internal fragmentation, where fixed-size allocation leaves wasted space inside allocated blocks, and external fragmentation, where variable-sized allocation leaves unusable gaps between allocated blocks. Paging directly addresses external fragmentation by standardizing block sizes, at the cost of some internal fragmentation; segmentation and dynamic heap allocators face the opposite tradeoff. Modern systems combine multiple techniques — buddy allocation, slab allocators, compaction, coalescing — to keep both forms of fragmentation manageable, but neither can be eliminated entirely without cost, which is why fragmentation remains a permanent, actively-managed concern in every operating system and language runtime.

Frequently Asked Questions

Q: Can fragmentation be completely eliminated? Not entirely, without significant tradeoffs. Paging eliminates external fragmentation but introduces internal fragmentation. Compacting garbage collectors can reduce fragmentation close to zero but at the cost of pause times and CPU overhead for relocating objects and updating references.

Q: Which is worse, internal or external fragmentation? Neither is universally “worse” — it depends on context. External fragmentation can cause outright allocation failures despite available memory, which is often more disruptive. Internal fragmentation is more predictable and bounded, making it easier to plan around.

Q: Does fragmentation affect SSDs and flash storage differently? File system fragmentation (a related but distinct concept from memory fragmentation) affects SSDs far less than spinning disks because SSDs have no seek time penalty, though excessive fragmentation can still slightly affect wear-leveling algorithms and write amplification.

Q: How does garbage collection help with fragmentation? Compacting garbage collectors (used in Java’s HotSpot JVM, for example) periodically move live objects together, eliminating gaps left by dead objects — directly combating external-fragmentation-like effects within the managed heap.

Q: Why does Linux use the buddy system for physical memory? Because it provides fast (O(log n)) allocation and coalescing with a simple, predictable structure, which is important given how frequently the kernel itself allocates and frees physical page frames.

References

  • Silberschatz, Galvin, Gagne — Operating System Concepts, Chapter on Memory Management
  • Linux kernel documentation — Documentation/admin-guide/mm/, buddy allocator source (mm/page_alloc.c)
  • glibc manual — Memory Allocation (malloc internals)
  • Microsoft Docs — Low Fragmentation Heap
  • Knuth, D. — The Art of Computer Programming, Vol. 1, on allocation strategies (first-fit, best-fit, worst-fit)
Total
0
Shares

Leave a Reply

Previous Post
What is the role of a page table in virtual memory

What Is the Role of a Page Table in Virtual Memory?

Next Post
Explain the concept of memory segmentation

Explaining the Concept of Memory Segmentation

Related Posts