Python Memory Management and Garbage Collection

Learn how Python manages memory with reference counting and cyclic garbage collection, plus practical tools for debugging memory issues.

8 min read

Python manages memory automatically using two complementary mechanisms. Reference counting tracks how many references point to each object and frees objects immediately when the count reaches zero. A cyclic garbage collector runs periodically to find and free groups of objects that reference each other in cycles.

The article on the Python object model introduced the idea that names are labels pointing at objects. This article covers what happens to those objects when no labels remain and how Python prevents memory from leaking due to reference cycles.

Understanding memory management helps you write code that uses memory efficiently, debug leaks, and choose the right data structures for long-running applications.

How reference counting works

Every Python object carries a reference count that tracks how many references point to it. When you create an object, its count starts at one. Assigning a new name, passing the object as an argument, or storing it in a container all increase the count.

When a reference goes out of scope, is reassigned, or is explicitly deleted with del, the count decreases. The moment the count hits zero, Python deallocates the object immediately.

The sys.getrefcount function returns the current reference count. Note that the function call itself adds a temporary reference, so the count is always one higher than expected.

pythonpython
import sys
 
data = [1, 2, 3]
print(sys.getrefcount(data))
 
alias = data
print(sys.getrefcount(data))
 
del alias
print(sys.getrefcount(data))

The getrefcount function counts its own argument reference, so the reported count is always one higher than the actual number of external references. Creating and deleting aliases changes the count predictably.

texttext
2
3
2

Reference counting is deterministic and predictable. Objects are freed as soon as they become unreachable, which means resources held by objects are released promptly without waiting for a garbage collection cycle.

The problem of reference cycles

Reference counting alone cannot handle cycles. When two objects reference each other, their counts never reach zero even when nothing outside the cycle references them.

The simplest cycle is a list that contains itself, but real cycles occur in parent-child relationships, doubly-linked structures, and observer patterns.

pythonpython
import gc
 
class Node:
    def __init__(self, name):
        self.name = name
        self.child = None
 
a = Node("A")
b = Node("B")
a.child = b
b.child = a
 
del a, b
unreachable = gc.collect()
print(unreachable > 0)

After deleting a and b, the nodes are unreachable but still reference each other. Calling gc.collect() detects and frees them, so the return value is greater than zero. The exact count varies by environment because it includes every unreachable object the collector finds, not just the two nodes.

texttext
True

The gc module provides the collect function to trigger collection manually. In normal operation, Python runs the collector automatically when allocation thresholds are exceeded.

Working with the gc module

The gc module gives you control over the cyclic garbage collector. You can disable it, tune thresholds, inspect tracked objects, and get detailed collection statistics.

Disabling the collector is sometimes useful for performance-critical sections. Re-enable it afterward to prevent unbounded memory growth.

pythonpython
gc.disable()
try:
    data = [1] * 10_000_000
finally:
    gc.enable()

The get_objects function returns all objects tracked by the collector. This is useful for debugging but can be slow in large programs.

pythonpython
all_lists = [obj for obj in gc.get_objects() if isinstance(obj, list)]
print(f"Tracked lists: {len(all_lists)}")

The garbage collector tracks container objects that can participate in cycles. Immutable objects like integers and strings are not tracked because they cannot form cycles.

Using weak references to prevent cycles

A weak reference lets you refer to an object without increasing its reference count. The object can be garbage collected even while weak references to it exist.

The weakref module provides several tools. The ref function creates a single weak reference. The WeakValueDictionary creates a mapping where values are held weakly.

pythonpython
import weakref
 
class Cache:
    def __init__(self):
        self._data = weakref.WeakValueDictionary()
 
    def store(self, key, value):
        self._data[key] = value
 
    def get(self, key):
        return self._data.get(key)
 
class Payload:
    def __init__(self, value):
        self.value = value
 
cache = Cache()
data = Payload([1, 2, 3])
cache.store("result", data)
print(cache.get("result").value)
 
del data
print(cache.get("result"))

After deleting the only strong reference to data, the cache entry disappears automatically. The weak reference did not prevent collection. Note that plain lists, dicts, and other built-in types cannot be weakly referenced directly; only custom class instances and a handful of built-ins support it, which is why the cached value here is a small wrapper class.

texttext
[1, 2, 3]
None

Weak references are ideal for caches, observer patterns, and any situation where you need to track objects without owning them.

The del finalizer and its limitations

The del method is called when an object is about to be destroyed. It is intended for cleanup, but there are significant limitations with cycles.

Since Python 3.4, the cyclic collector can safely finalize and free objects in a cycle even when more than one of them defines del. The real limitation is order: you cannot control which object's del runs first, so del methods should not depend on the state of other objects in the same cycle.

pythonpython
class Resource:
    def __del__(self):
        print("cleanup")
 
r = Resource()
del r

The single object is collected normally because there is no reference cycle keeping it alive. The del finalizer method runs immediately and prints its cleanup message before deallocation.

texttext
cleanup

For reliable resource cleanup, prefer context managers with the with statement over del. Context managers guarantee that exit runs at a predictable time regardless of how the block is exited.

The article on weak references in Python covers more advanced weakref patterns, including finalization callbacks that work correctly with reference cycles.

Rune AI

Rune AI

Key Insights

  • Python uses reference counting for immediate deallocation and a cycle detector for circular references.
  • The gc module provides collection control, threshold tuning, and leak debugging.
  • Reference cycles prevent objects from being freed by reference counting alone.
  • Weak references (weakref module) let you reference objects without keeping them alive.
  • The del finalizer is unpredictable in cycles; prefer context managers for resource cleanup.
RunePowered by Rune AI

Frequently Asked Questions

How does Python's reference counting work?

Every Python object has a reference count that tracks how many references point to it. When you assign a variable, pass an argument, or store an object in a container, the count increases. When a reference goes out of scope or is deleted, the count decreases. When the count reaches zero, Python immediately frees the object's memory. This is different from languages that rely solely on a tracing garbage collector.

What are weak references and when should I use them?

A weak reference lets you reference an object without increasing its reference count. The object can be garbage collected even if weak references to it exist. The weakref module provides WeakValueDictionary and WeakSet for building caches that do not keep objects alive. Use weak references for caches, observer patterns, and any situation where you need to track objects without preventing their collection.

Conclusion

Python's memory management combines deterministic reference counting with a cyclic garbage collector for handling reference cycles. The gc module gives you visibility and control over collection. Weak references let you build caches and registries that do not create leaks. Understanding these mechanisms helps you write code that manages memory efficiently and debug issues when they arise.