micropython-gestion-memoria-garbage-collector

Memory Management in MicroPython: GC and mem_info

  • 6 min

The memory in MicroPython is a limited resource that we must control to avoid runtime errors.

If you come from programming on a PC, you’ve probably never worried about RAM. You have gigabytes available. But in the world of microcontrollers, reality is very different.

  • ESP8266: Barely ~40 KB free for the user.
  • Raspberry Pi Pico: About ~200 KB.
  • ESP32: About ~520 KB (though fragmented).

It might seem enough, but MicroPython is a high-level language: every integer, every list, and every object consumes resources. If we are not careful, we will soon encounter the dreaded error:

MemoryError: memory allocation failed

In this article, we are going to open the hood to see how MicroPython manages memory and how to use the Garbage Collector to our advantage.

The Heap and the Stack

Without delving into deep computer engineering, we must know that RAM is mainly divided into two areas:

  1. Stack: It’s a rigid and small area. Function calls and simple local variables are stored here. It cleans itself upon exiting the function.
  2. Heap: It’s the “pool” of free memory. This is where large objects live: lists, dictionaries, class instances, and your program’s own code.

When you create a list my_list = [1, 2, 3], MicroPython looks for a free spot in the Heap and occupies it. The problem arises when we delete things and create new ones haphazardly.

Diagnosis: micropython.mem_info()

You can’t optimize what you can’t measure. MicroPython includes a very powerful diagnostic tool within the micropython module.

import micropython
import gc

# Force a cleanup before measuring
gc.collect()

# Print the memory report
micropython.mem_info()
Copied!

The output will look something like this:

stack: 2128 out of 15360
GC: total: 2072064, used: 4224, free: 2067840
 No. of 1-blocks: 14, 2-blocks: 6, max blk sz: 264, max free sz: 129218
Copied!

What does this mean?

  • Stack: It tells us how much of the stack we are using. If this fills up (for example, with infinite recursion), we will get a RuntimeError: maximum recursion depth exceeded.
  • GC total: The total available RAM for the Heap.
  • used / free: What we are consuming and what remains.

If we run micropython.mem_info(1) (with a 1 as an argument), it will give us a visual map of the memory, where each character represents a memory block and its state. It is useful for visually seeing fragmentation.

The Garbage Collector (gc)

Unlike C++, where you have to free memory manually (free()), MicroPython has an automatic janitor: the Garbage Collector (GC).

Its job is to periodically traverse memory, look for objects that are no longer used (that have no variables pointing to them), and free that space so it can be reused.

When does the GC act?

By default, the GC is lazy. It only activates when it tries to allocate new memory and finds no space.

  1. You request to create a large list.
  2. The system sees it doesn’t fit.
  3. It pauses your program.
  4. It runs the GC (cleans up).
  5. It tries to allocate the list again.

This automatic process has a risk: it takes time (several milliseconds). If this happens right when you are generating a delicate PWM signal or reading a fast sensor, you can have timing issues.

Manual GC Control

For advanced projects, we take control. It’s good practice to import the gc module and manage the cleanup ourselves.

import gc

# 1. Query free memory (in bytes)
print("Free memory:", gc.mem_free())

# 2. Query used memory
print("Used memory:", gc.mem_alloc())

# 3. Force a MANUAL cleanup
# Highly recommended after importing large libraries
# or before starting critical processes.
gc.collect() 
print("Cleanup performed.")
Copied!

Disable the GC in Critical Zones

If you have a piece of critical code where you cannot afford the processor to stop for cleanup (e.g., controlling neopixels or stepper motors at high speed), you can temporarily disable it.

import gc

gc.disable() # Turn off the collector

# --- START CRITICAL ZONE ---
# Code here must be fast and not create many objects
# If you fill the RAM here, the program will fail without attempting cleanup
do_fast_things()
# --- END CRITICAL ZONE ---

gc.enable() # Reactivate it
gc.collect() # Clean up what got dirty
Copied!

Memory Fragmentation

Sometimes gc.mem_free() tells you that you have 20 KB free, but you try to create a 5 KB list and get an error. Why?

Because memory is not contiguous. Imagine a parking lot (the RAM). You have 20 free spots, but they are scattered (one here, two there). If you try to park a bus (a large object), it won’t fit, even though the total free spots are sufficient.

How to Avoid Fragmentation

  1. Avoid creating and destroying objects in loops:
# Creates several temporary strings each iteration
while True:
    print("Temperature: " + str(read_sensor()))

# Avoid concatenation if you just want to display them
while True:
    print("Temperature:", read_sensor())
Copied!
  1. Pre-allocate memory: If you know you’re going to need a list of 1000 elements, create it at the beginning, don’t do .append() gradually.
# Pre-allocated buffer
my_buffer = bytearray(1000)
Copied!
  1. Use const(): For values that don’t change, the micropython.const() function allows the compiler to treat them as constants. It can avoid lookups and unnecessary global objects, though the actual savings depend on the port and the name used.
from micropython import const
DELAY_MS = const(100)
Copied!
  1. Execute gc.collect() at controlled points: It can free objects that are no longer used and prevents collection from happening at the worst possible moment. It does not compact memory or move objects that are still alive, so it does not eliminate fragmentation by itself.

How to Avoid Memory Problems

Memory in MicroPython is a precious resource.

  • Use micropython.mem_info() to know what is happening.
  • Use gc.collect() strategically to clean “when you want,” not “when the system crashes.”
  • Be wary of fragmentation: try to reuse objects instead of constantly creating new ones.

By controlling the GC, you can run surprisingly complex programs on chips costing less than 5 euros.