Python Memory Management: Reference Counting and Garbage Collection
High-level languages handle memory allocation automatically, yet inefficient data usage or circular references can cause hidden memory leaks in large applications. Python uses a dual-mechanism memory manager combining Reference Counting with a Generational Garbage Collector (GC).
This article covers memory allocation, object referencing, how Python detects circular references, and manually managing GC cycles for maximum performance.
Key Takeaways
- Python manages memory automatically via reference counting as its primary mechanism.
- Every Python object tracks its reference count in its
PyObjectheader (ob_refcnt). - When an object’s reference count reaches zero, its memory is deallocated instantly.
- The Generational Garbage Collector detects and frees unreachable cyclic references across three generations (Gen 0, Gen 1, Gen 2).
1. Reference Counting Fundamentals
Whenever you assign an object to a variable, pass it to a function, or store it in a container, its reference count increments. Inspect reference counts using sys.getrefcount().
Python
import sys
# Instantiate a string object
payload = ["Eduzik Core Data"]
# Query reference count (Note: getrefcount adds 1 temporary reference)
print(f"Initial references: {sys.getrefcount(payload) - 1}") # Output: 1
# Creating secondary references
alias = payload
container = [payload]
print(f"Active references: {sys.getrefcount(payload) - 1}") # Output: 3
# Deleting references
del alias
del container
print(f"Final references: {sys.getrefcount(payload) - 1}") # Output: 1
2. Cyclic References and Generational Garbage Collection
Reference counting fails when two or more objects reference each other, creating a reference cycle. Even if external variables are deleted, the internal reference counts never hit zero.
Python
import gc
class Node:
def __init__(self, name):
self.name = name
self.link = None
# Create circular reference
node_a = Node("Alpha")
node_b = Node("Beta")
node_a.link = node_b
node_b.link = node_a
# Delete primary variables
del node_a
del node_b
# Objects remain in memory due to mutual references!
# Python's Generational GC steps in to clean isolated cycles:
unreachable_objects = gc.collect()
print(f"[GC] Reclaimed unreachable cyclic objects: {unreachable_objects}")
3. How the Generational GC Works
Python categorizes objects into three generations based on their age and survival across collection cycles:
| Generation | Object Age | Collection Frequency | Threshold Trigger |
| Generation 0 | Newly allocated objects | Very High | Exceeds threshold[0] allocations |
| Generation 1 | Survived Gen 0 collections | Medium | Exceeds threshold[1] Gen 0 cycles |
| Generation 2 | Long-lived objects / Singletons | Low (Full GC run) | Exceeds threshold[2] Gen 1 cycles |
4. Tuning Garbage Collection for Performance
In latency-critical applications (such as high-frequency trading or real-time gaming engines), non-deterministic GC pauses can cause performance spikes. You can temporarily disable or tune GC runs.
Python
import gc
# Inspect current GC thresholds (Gen0, Gen1, Gen2)
print(f"Default GC Thresholds: {gc.get_threshold()}")
# Increase thresholds to delay GC cycles during high-throughput execution
gc.set_threshold(1000, 15, 15)
# Temporarily disable GC during critical execution loops
gc.disable()
# ... Perform high-speed operations ...
gc.enable() # Re-enable GC
gc.collect() # Trigger manual collection sweep
Conclusion
Understanding Python memory management helps you build memory-efficient applications and diagnose performance bottlenecks. Rely on reference counting for instant cleanup, monitor reference cycles, and tune the generational GC when building high-performance Python services.
Frequently Asked Questions
What is the difference between del variable and memory deallocation?
del variable removes the variable name binding and decrements the target object’s reference count. Memory is deallocated only when that reference count reaches zero.
What is the Small Object Allocator (PyMalloc)?
Python uses PyMalloc for small objects (up to 512 bytes) to bypass OS-level memory allocation overhead. It manages memory in arenas, pools, and blocks for fast memory allocation.
Do slot classes (__slots__) save memory in Python?
Yes. Defining __slots__ inside a class prevents the creation of an internal __dict__ attribute dictionary for each object instance, significantly reducing memory footprint when instantiating millions of objects.

