HomeAbout UsContact Us

Heap Fragmentation and Memory Management Strategies in RTOS-Based Embedded Systems

By embeddedSoft
Published in Embedded OS
June 13, 2026
5 min read
Heap Fragmentation and Memory Management Strategies in RTOS-Based Embedded Systems

Table Of Contents

01
What is Heap Fragmentation?
02
Why RTOS Systems Are Particularly Vulnerable
03
Common Anti-Fragmentation Strategies
04
RTOS-Specific Heap Implementations
05
Detecting and Monitoring Fragmentation
06
Practical Recommendations
07
References

Dynamic memory allocation is a double-edged sword in embedded systems. While malloc() and free() offer flexibility, their uncontrolled use in RTOS-based firmware can lead to heap fragmentation—a silent killer that causes sporadic allocation failures in systems that ran perfectly for hours or days. Understanding heap fragmentation and implementing robust memory management strategies is essential for building reliable embedded software.

What is Heap Fragmentation?

Heap fragmentation occurs when free memory is scattered throughout the heap in small, non-contiguous blocks. Over time, allocations and frees of varying sizes create a “Swiss cheese” pattern in memory. Even though the total free memory may be sufficient, no single contiguous block is large enough to satisfy a new allocation request.

There are two types of fragmentation:

  • External fragmentation: Free memory exists but is broken into small blocks scattered across the heap. The largest free block may be much smaller than the total free space.
  • Internal fragmentation: Allocated blocks are larger than requested because the allocator rounds up to fixed-size boundaries, wasting space within each block.

The diagram below illustrates how a heap evolves from a clean state to a fragmented one over repeated allocation and deallocation cycles:

HEAP STATE OVER TIME
====================
INITIAL STATE (after boot):
+--------------------------------------------------------------+
| FREE |
| (64 KB total) |
+--------------------------------------------------------------+
AFTER ALLOCATIONS (Tasks A-F allocate the entire heap):
+------+--------+------+------+------+-------------------------+
|Task A| Task B |Task C|Task D|Task E| Task F |
| 4 KB | 8 KB | 2 KB | 4 KB | 6 KB | 40 KB |
+------+--------+------+------+------+-------------------------+
AFTER SOME FREES (Tasks A, C, and D freed, others alive):
+------+--------+------+------+------+-------------------------+
| FREE | Task B | FREE | FREE |Task E| Task F |
| 4 KB | 8 KB | 2 KB | 4 KB | 6 KB | 40 KB |
+------+--------+------+------+------+-------------------------+
^ ^ ^
| | |
+--- Fragmented small blocks
THE PROBLEM: A new 6 KB allocation FAILS despite 10 KB free!
The 4 KB, 2 KB, and 4 KB free blocks are all too small individually.
Task B separates the first 4 KB block from the others.
AFTER COALESCING (heap_4.c style -- adjacent free blocks merged):
The 2 KB and 4 KB free blocks are adjacent, so they merge into
a single 6 KB block. The first 4 KB block remains isolated.
+------+--------+-------------+------+-------------------------+
| FREE | Task B | FREE (6 KB) |Task E| Task F |
| 4 KB | 8 KB | (2KB + 4KB) | 6 KB | 40 KB |
+------+--------+-------------+------+-------------------------+
OK A 6 KB allocation now succeeds from the merged 6 KB block!
OVER TIME, WITHOUT COALESCING (heap_2.c style -- never merges):
If Task E and F are later freed, their blocks remain separate.
+------+--------+------+------+------+-------------------------+
| FREE | Task B | FREE | FREE | FREE | FREE |
| 4 KB | 8 KB | 2 KB | 4 KB | 6 KB | 40 KB |
+------+--------+------+------+------+-------------------------+
^ ^ ^ ^
| | | |
These adjacent free blocks are never merged!
(A 52 KB allocation would fail here)

In RTOS environments, fragmentation is especially problematic because tasks allocate and free memory at different rates and with different size patterns.

Why RTOS Systems Are Particularly Vulnerable

In a bare-metal super-loop architecture, allocation patterns are typically predictable—most allocations happen at startup, and few or none occur during the main loop. RTOS systems are different:

  1. Concurrent allocation patterns: Multiple tasks allocate and free memory independently and asynchronously, creating unpredictable interleaving of allocation sizes.
  2. Long uptime requirements: Embedded systems often run for years without restarting. Even a slow fragmentation rate becomes critical over time.
  3. Deterministic timing requirements: A blocked allocation in a high-priority safety-critical task can cause missed deadlines.
  4. Limited memory: Microcontrollers may have only tens or hundreds of kilobytes of RAM, leaving almost no room for fragmentation overhead.

Consider a scenario where Task A allocates 64-byte blocks and Task B allocates 128-byte blocks. If Task A frees every other block and Task B frees blocks at a different rate, the heap quickly becomes a mosaic of used and free regions that cannot satisfy a new 256-byte allocation—even when 70% of the heap is free.

Common Anti-Fragmentation Strategies

1. Memory Pool Allocation

The most robust approach replaces generic malloc()/free() with fixed-size block allocators. A memory pool pre-allocates an array of fixed-size blocks and maintains a free list. All allocations return blocks of identical size, eliminating external fragmentation entirely.

class MemoryPool {
uint8_t* pool;
uint32_t block_size;
uint32_t block_count;
void* free_list;
public:
// block_size must be >= sizeof(void*) and aligned to alignof(void*)
// to avoid misaligned access faults (e.g., HardFault on Cortex-M0/M0+).
MemoryPool(void* memory, uint32_t block_size, uint32_t count)
: block_size(block_size), block_count(count)
{
pool = static_cast<uint8_t*>(memory);
// Build free list in forward order (Block 0 at head)
free_list = nullptr;
for (uint32_t i = count; i > 0; --i) {
void* block = pool + ((i - 1) * block_size);
*reinterpret_cast<void**>(block) = free_list;
free_list = block;
}
}
void* allocate()
{
if (!free_list) return nullptr; // Pool exhausted
void* block = free_list;
free_list = *reinterpret_cast<void**>(free_list);
return block;
}
void deallocate(void* block)
{
if (block) {
*reinterpret_cast<void**>(block) = free_list;
free_list = block;
}
}
};

Memory pools are deterministic—both allocation and deallocation are O(1) operations with no searching or coalescing overhead. In an RTOS environment, pool operations must be wrapped in a critical section or mutex to ensure thread safety across concurrent tasks.

The internal structure of a memory pool with 5 blocks of 64 bytes each:

MEMORY POOL (5 x 64-byte blocks)
=================================
FREE LIST (initially all blocks free, head at Block 0):
head --> +----------+ +----------+ +----------+
| Block 0 |---->| Block 1 |---->| Block 2 |--> ...
| next* | | next* | | next* |
+----------+ +----------+ +----------+
+----------+ +----------+
| Block 3 |---->| Block 4 |--> NULL
| next* | | next* |
+----------+ +----------+
* "next" pointer is overlaid in the block's first sizeof(void*) bytes.
When allocated, the caller gets the full 64 bytes (zero overhead).
AFTER allocate() x 3 (returns Block 0, Block 1, Block 2):
head --> +----------+ +----------+
| Block 3 |---->| Block 4 |--> NULL
| next* | | next* |
+----------+ +----------+
Allocated: Block 0, Block 1, Block 2 <-- returned to caller
AFTER deallocate(Block 1) -- pushed to head of free list:
head --> +----------+ +----------+ +----------+
| Block 1 |---->| Block 3 |---->| Block 4 |--> NULL
| next* | | next* | | next* |
+----------+ +----------+ +----------+
OK Zero fragmentation -- every alloc returns a full 64-byte block
OK O(1) pointer unlink/link -- no searching, no merging
OK Deterministic -- critical for real-time tasks

2. Separate Pools for Common Sizes

For systems requiring multiple allocation sizes, use a set of memory pools with different block sizes (e.g., 32, 64, 128, 256 bytes). The allocator routes each request to the smallest pool that can satisfy it. This “size-class” approach is used by commercial RTOS implementations such as µC/OS-III memory partitions and can be implemented as a custom allocator layer on top of any RTOS. Note that FreeRTOS’s built-in heap schemes (heap_1 through heap_5) are general-purpose allocators and do not use this pattern natively.

3. Static Allocation at Compile Time

The safest approach for safety-critical systems is to eliminate dynamic allocation entirely. All objects—task stacks, message buffers, state machines—are allocated statically at compile time. MISRA C:2012 Directive 4.12 states that “dynamic memory allocation shall not be used,” and Rule 21.3 specifically prohibits malloc, calloc, realloc, and free. FreeRTOS supports this pattern through configSUPPORT_STATIC_ALLOCATION, which enables APIs like xTaskCreateStatic() and xQueueCreateStatic() for creating kernel objects with statically allocated memory.

4. Region-Based (Arena) Allocators

An arena allocator pre-allocates a large contiguous region and hands out memory sequentially. Deallocation resets the entire region at once. This pattern is useful for processing pipelines where all temporary data has a well-defined lifecycle.

RTOS-Specific Heap Implementations

Most RTOS kernels provide multiple heap implementation options:

Heap VariantmallocfreeCoalesces Adjacent?Deterministic?
heap_1YESNO-YES
heap_2YESYESNONO - best-fit search
heap_3YESYES(libc)NO (libc)
heap_4YESYESYESNO - first-fit search + merge cost
heap_5YESYESYESNO - first-fit search + merge cost, multi-region

Recommended choice by use case:

Use CaseRecommended Heap
Safety-critical, no freeingheap_1 or static alloc
Fixed-size blocks onlyheap_2 (or memory pools)
Variable-size, general purposeheap_4 (with monitoring)
Disjoint memory regions (RAM1 + RAM2 + external SDRAM)heap_5

Note: heap_3 is rarely the right choice — wraps libc malloc/free with mutex protection but inherits all fragmentation behavior.

  • FreeRTOS heap_1: Simple bump-pointer allocator—only allocate, no free. The only truly deterministic option. Suitable for systems that never deallocate.
  • FreeRTOS heap_2: Uses a best-fit algorithm to find the closest-sized free block. Supports free but does not coalesce adjacent free blocks. Works well when repeatedly allocating and freeing blocks of the same size. Now considered legacy; prefer heap_4 or custom memory pools for new designs.
  • FreeRTOS heap_3: Wraps standard malloc()/free() with scheduler suspension for thread safety. Subject to all libc fragmentation behavior.
  • FreeRTOS heap_4: Uses a first-fit algorithm and coalesces adjacent free blocks into larger blocks. Best general-purpose option but non-deterministic due to variable-time free-block search during allocation.
  • FreeRTOS heap_5: Extends heap_4 to support non-contiguous memory regions spanning disjoint address ranges (e.g., internal SRAM + external SDRAM).

For production systems, static allocation or custom memory pools are preferred. heap_4 is the best general-purpose option when variable-size allocations are unavoidable. heap_2 is legacy and heap_3 should be avoided in new designs.

Detecting and Monitoring Fragmentation

Even with good design practices, it is wise to monitor heap health at runtime:

// FreeRTOS provides vPortGetHeapStats() which populates the HeapStats_t
// structure (available in heap_4 and heap_5). Key fields include:
// xAvailableHeapSpaceInBytes - total free heap bytes
// xSizeOfLargestFreeBlockInBytes - largest contiguous free block
// xSizeOfSmallestFreeBlockInBytes - smallest free block
// xNumberOfFreeBlocks - count of free blocks (fragmentation indicator)
// xMinimumEverFreeBytesRemaining - low-water mark of free heap
// xNumberOfSuccessfulAllocations - lifetime allocation count
// xNumberOfSuccessfulFrees - lifetime free count
void log_heap_stats()
{
HeapStats_t stats;
vPortGetHeapStats(&stats);
// Fragmentation index: 0.0 = no fragmentation, 1.0 = fully fragmented
// Formula: 1.0 - (largest_free_block / total_free)
float frag_index = 0.0f;
if (stats.xAvailableHeapSpaceInBytes > 0) {
frag_index = 1.0f - ((float)stats.xSizeOfLargestFreeBlockInBytes
/ (float)stats.xAvailableHeapSpaceInBytes);
}
printf("Heap: %u free / %u total (min ever: %u)\n",
(unsigned)stats.xAvailableHeapSpaceInBytes,
(unsigned)configTOTAL_HEAP_SIZE,
(unsigned)stats.xMinimumEverFreeBytesRemaining);
printf("Fragmentation index: %.2f\n", frag_index);
printf("Largest block: %u / Free blocks: %u\n",
(unsigned)stats.xSizeOfLargestFreeBlockInBytes,
(unsigned)stats.xNumberOfFreeBlocks);
}

The fragmentation index is calculated as 1.0 - (largest_free_block / total_free_memory). A value of 0.0 means all free memory is in one contiguous block (no fragmentation). A value approaching 1.0 signals that free memory is scattered into many small blocks—the largest free block is small relative to total free memory. Monitoring this value during development and in field-deployed units provides early warning of impending allocation failures.

Practical Recommendations

  1. Never use malloc()/free() in time-critical paths: The non-deterministic execution time makes them unsuitable for hard real-time tasks. Consider a TLSF (Two-Level Segregated Fit) allocator if you need O(1) general-purpose allocation with bounded worst-case time.
  2. Analyze allocation patterns during development: Log all allocation sizes and lifetimes to identify pool configurations.
  3. Set hard limits on dynamic allocation: Use configurable pool sizes that are validated during testing.
  4. Watch for the “gradual creep” failure mode: Systems that fail after hours or days of operation often suffer from fragmentation-induced allocation failure.
  5. Prefer message passing over shared heap memory: Use RTOS queues and message buffers instead of having tasks share dynamically allocated structures.
  6. Protect pool operations in multi-task environments: Wrap memory pool allocate/deallocate calls with a mutex, critical section, or scheduler suspension to prevent corruption from concurrent access.
  7. Consider TLSF for real-time general-purpose allocation: The Two-Level Segregated Fit allocator provides O(1) bounded-time malloc/free with good fragmentation resistance, and is widely used in automotive and aerospace embedded systems.

Heap fragmentation is one of the most insidious bugs in embedded systems because it manifests as intermittent failures that are nearly impossible to reproduce in short test runs. By adopting pool-based allocation, monitoring heap health, and designing for deterministic memory usage, firmware engineers can eliminate this entire class of runtime failures from their RTOS-based systems.

References

  1. Buttazzo, G. C. (2011). Hard Real-Time Computing Systems: Predictable Scheduling Algorithms and Applications (3rd ed.). Springer. ISBN: 978-1-4614-3675-1. (Background on deterministic timing requirements in real-time systems.)

  2. FreeRTOS Kernel Documentation — Memory Management. Amazon Web Services. https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/09-Memory-management/01-Memory-management

  3. Barr, M. & Massa, A. (2006). Programming Embedded Systems: With C and GNU Development Tools (2nd ed.). O’Reilly Media. Chapter 6: Memory.

  4. MISRA C:2012 Guidelines for the Use of the C Language in Critical Systems. MISRA Consortium. https://misra.org.uk/

  5. Wilson, P. R., Johnstone, M. S., Neely, M., & Boles, D. (1995). “Dynamic Storage Allocation: A Survey and Critical Review.” International Workshop on Memory Management (IWMM), LNCS 986, Springer-Verlag, 1–116.

  6. Labrosse, J. J. (2012). µC/OS-III: The Real-Time Kernel for the STM32 ARM Cortex-M3. Micrium Press. Chapter 10: Memory Management.

  7. Masmano, M., Ripoll, I., Crespo, A., & Real, J. (2004). “TLSF: A New Dynamic Memory Allocator for Real-Time Systems.” Proceedings of the 16th Euromicro Conference on Real-Time Systems (ECRTS), IEEE, 79–86.


Tags

RTOSmemory managementheap fragmentationembedded systemsdynamic memory

Share


Previous Article
DMA Programming in Embedded C for High-Throughput Data Transfer
embeddedSoft

embeddedSoft

Embedded Systems Articles by Jithin Tom & Hermes (AI Agent)

Related Posts

RTOS Concepts - Tasks, Semaphores, and Mutexes
RTOS Concepts - Tasks, Semaphores, and Mutexes
May 09, 2026
3 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media