What Are Immortal Objects in Python?

Immortal objects are a core memory management feature introduced in Python 3.12 via PEP 683 that permanently bypass Python’s standard reference counting and garbage collection mechanisms. By design, an immortal object remains in memory throughout the entire lifecycle of the Python runtime, with a reference count that never changes regardless of how many times it is referenced or dereferenced. This article explains the fundamentals of immortal objects, the engineering problems they solve, and their crucial role in modernizing Python's multi-core performance.

How Python Memory Management Traditionally Works

Standard Python (CPython) primarily manages memory through reference counting. Every standard object in Python contains a header field called ob_refcnt. When an object is referenced, this counter increments; when a reference goes out of scope or is deleted, it decrements. When the counter reaches zero, Python deallocates the object's memory immediately.

While this system is simple and deterministic, it creates a fundamental limitation: even purely read-only operations must modify the object's internal state to update the reference counter.

What Are Immortal Objects?

Immortal objects are objects whose reference counts are treated as immutable by the runtime. CPython achieves this by setting a specific bit flag or assigning an extraordinarily large value to the object’s reference count field that signals to the runtime to skip incrementing or decrementing operations.

Because their reference counts never change:

Examples of immortal objects in current versions of Python include global singletons like None, True, False, empty tuples, small integers, and internal interned strings.

Why Immortal Objects Were Introduced

Immortal objects were introduced primarily to solve architectural bottlenecks related to scalability, performance, and concurrency.

1. Enabling True Shared Memory Across Runtimes

Prior to immortal objects, sharing common objects (like None or small numbers) across multiple threads or isolated subinterpreters was unsafe. Because reading an object altered its reference count, two threads accessing the same object simultaneously would cause a data race on ob_refcnt, requiring locks or thread-specific copies. Immortal objects make singletons truly immutable, enabling multiple interpreters or threads to safely share the same memory without locks.

2. Fixing Copy-on-Write (CoW) Inefficiencies

When Python uses Unix process forking (common in web servers like Gunicorn or Celery), child processes initially share the parent process's memory pages via Copy-on-Write. However, under traditional reference counting, merely reading global objects in a child process altered their reference counts. This caused the operating system to duplicate the entire memory page containing the object, leading to massive memory bloat. Immortal objects prevent reference count mutations, keeping memory pages clean and dramatically reducing the real memory footprint of forked applications.

3. Maximizing CPU Cache Efficiency

Constantly writing to reference count fields invalidates CPU caches, even when an algorithm is only reading data. Immortal objects preserve CPU cache lines by eliminating unnecessary writes, leading to minor baseline execution speedups in cache-sensitive workloads.

4. Paving the Way for the Free-Threaded Build (No-GIL)

The introduction of immortal objects is a foundational prerequisite for PEP 703 (the removal of the Global Interpreter Lock). In a free-threaded Python environment, ubiquitous objects accessed by dozens of threads simultaneously would otherwise suffer severe lock contention or require atomic operations that degrade performance. Making universal objects immortal eliminates synchronization overhead entirely for those objects.