Memory Management with Linked List: Maximizing Memory Allocation

Rizki Nurhidayat
CodeX
Published in
4 min readJun 27, 2024

Memory management is a crucial aspect of software development. The way we manage memory directly impacts the performance of an application. One method used to maximize memory allocation is through the use of a linked list data structure. In this article, we will discuss how linked lists can be used for memory management, their workings, benefits, and include a simple simulation in the Go (Golang) programming language.

What is a Linked List?

A linked list is a data structure consisting of a series of elements called nodes. Each node has two main components:

  1. Data: Stores information.
  2. Pointer: Points to the next node in the list.

The main advantage of linked lists is their ability to allocate and free memory dynamically without the need to shift other elements as in an array.

Why Treat Memory as Blocks?

Treating memory as blocks has several important purposes and functions in memory management. Here are the reasons why this approach is used and what the functions of memory blocks are:

Memory Fragmentation

  • External Fragmentation: Occurs when there is enough total free memory but it is fragmented into small pieces, making it impossible to fulfill a large memory allocation request.
  • Internal Fragmentation: Occurs when allocated blocks are larger than the actual needs of the application, causing wasted memory within the blocks.

By treating memory as blocks, memory managers can reduce external fragmentation by combining adjacent free memory blocks into a larger block when memory is freed. This approach also allows for more efficient use of memory by allocating only the size needed by the application.

Ease of Management

  • Memory blocks make the allocation and deallocation process more structured and easier to track. Each block can be easily allocated or freed without having to move other memory.
  • Using a linked list, the memory manager can quickly find a sufficiently large memory block to fulfill requests without scanning the entire memory.

Efficiency and Flexibility

  • Using memory blocks allows the memory manager to handle allocation requests of various sizes with high flexibility.
  • Adjacent memory blocks can be merged when freed, reducing fragmentation and improving overall memory utilization.

How Linked List Works in Memory Management

  1. Initialization: Initially, the entire memory is considered a large unused block and inserted into the free list.
  2. Allocation: When memory is needed, the memory management algorithm searches for a sufficiently large block in the free list. If found, the block is split if necessary, and part of the block is moved to the allocated list.
  3. Deallocation: When memory is no longer needed, the block is moved back to the free list. If the newly freed block is adjacent to other free blocks, they can be merged into a single large block to avoid fragmentation.

Advantages of Using Linked List for Memory Management

  1. Flexibility: Allows dynamic allocation and deallocation of memory without needing to shift other elements.
  2. Efficiency: Minimizes memory fragmentation by merging adjacent free blocks.
  3. Simplicity: Relatively simple to implement compared to other memory management methods.

Simulation in Go (Golang)

Here is a simple code example simulating memory management using a linked list in Go:

package main

import (
"fmt"
)

// Node represents a block of memory
type Node struct {
size int
next *Node
}

// MemoryManager manages memory allocation and deallocation
type MemoryManager struct {
freeList *Node
allocatedList *Node
}

// NewMemoryManager creates a new MemoryManager
func NewMemoryManager(totalSize int) *MemoryManager {
return &MemoryManager{
freeList: &Node{size: totalSize},
}
}

// Allocate allocates a block of memory
func (m *MemoryManager) Allocate(size int) *Node {
var prev *Node
current := m.freeList

for current != nil {
if current.size >= size {
allocated := &Node{size: size}

if current.size == size {
if prev == nil {
m.freeList = current.next
} else {
prev.next = current.next
}
} else {
current.size -= size
}

allocated.next = m.allocatedList
m.allocatedList = allocated
return allocated
}
prev = current
current = current.next
}
return nil
}

// Deallocate deallocates a block of memory
func (m *MemoryManager) Deallocate(node *Node) {
// Remove from allocated list
var prev *Node
current := m.allocatedList

for current != nil {
if current == node {
if prev == nil {
m.allocatedList = current.next
} else {
prev.next = current.next
}
break
}
prev = current
current = current.next
}

// Add to free list
node.next = m.freeList
m.freeList = node
}

// Display prints the current state of free and allocated lists
func (m *MemoryManager) Display() {
fmt.Println("Free List:")
for n := m.freeList; n != nil; n = n.next {
fmt.Printf("Size: %d -> ", n.size)
}
fmt.Println("nil")

fmt.Println("Allocated List:")
for n := m.allocatedList; n != nil; n = n.next {
fmt.Printf("Size: %d -> ", n.size)
}
fmt.Println("nil")
}

func main() {
mm := NewMemoryManager(100)
fmt.Println("Initial state:")
mm.Display()

a1 := mm.Allocate(20)
fmt.Println("After allocating 20 units:")
mm.Display()

a2 := mm.Allocate(30)
fmt.Println("After allocating 30 units:")
mm.Display()

mm.Deallocate(a1)
fmt.Println("After deallocating 20 units:")
mm.Display()

a3 := mm.Allocate(10)
fmt.Println("After allocating 10 units:")
mm.Display()

mm.Deallocate(a2)
fmt.Println("After deallocating 30 units:")
mm.Display()

mm.Deallocate(a3)
fmt.Println("After deallocating 10 units:")
mm.Display()
}

Explanation of the Code:

  • Node: Represents a memory block with size and a pointer to the next block.
  • MemoryManager: Manages the list of free and allocated memory blocks.
  • Allocate: Allocates memory by finding a sufficiently large block in the free list and moving it to the allocated list.
  • Deallocate: Frees memory by moving the block from the allocated list to the free list.
  • Display: Shows the current status of the free and allocated lists.

Conclusion

Using linked lists for memory management is an effective and efficient method for dynamically managing memory allocation and deallocation. With this structure, we can minimize memory fragmentation and maximize the use of available memory. The simple implementation in Go demonstrates how these principles can be applied in practical code.

--

--

Rizki Nurhidayat
CodeX
Writer for

🌟 Hitting life full throttle, no brakes. 💥 📈 Up for every challenge, down for every adventure. 🌍 💡 Dropping truths, stirring pots.🔥 👊 Embracing Mondays