Python implementa un sistema de tres capas para la gestión automática de memoria. Esta implementación libera al programador de la complejidad de asignar y liberar memoria manualmente, permitiéndole enfocarse en la lógica de negocio. A diferencia de lenguajes de bajo nivel como C o C++, que requieren gestión manual de memoria ofreciendo control directo sobre el hardware (necesario para drivers y sistemas embebidos), Python prioriza la prevención de errores comunes de gestión manual y la productividad del desarrollador mediante automatización.
Este artículo describe CPython (la implementación estándar de Python). Otras implementaciones como PyPy, Jython e IronPython tienen sistemas diferentes de gestión de memoria.
Arquitectura de Gestión de Memoria
Python utiliza una estrategia de gestión de memoria en múltiples capas:
Capa 1: Sistema Operativo -> Objetos grandes (>512 bytes) malloc() / free()
Capa 2: PyMalloc (Allocator de Python) -> Objetos pequeños (≤512 bytes)
Capa 3: Object Allocators -> Código Python (listas, dicts, objetos, etc.)
¿Qué es un Allocator?
Un allocator es el sistema que asigna y libera bloques de memoria. Actúa como “administrador de memoria” decidiendo:
- Dónde ubicar nuevos datos en memoria
- Cómo organizar el espacio disponible
- Cuándo liberar memoria que ya no se usa
Capa 1: Allocator del Sistema Operativo
- En el nivel más bajo, Python usa
malloc()yfree()del sistema operativo.malloc(tamaño): “Memory Allocation” – Pide un bloque de memoria al sistema operativo.free(puntero): Libera un bloque de memoria previamente asignado.
- Se usa para objetos muy grandes (> 512 bytes).
// Ejemplo en C (lo que Python hace internamente)
int* numeros = malloc(1024); // Pedir 1024 bytes
// ... usar la memoria ...
free(numeros); // Devolver memoria al SO
Nota: Cuando se descarga Python (https://www.python.org) se obtiene CPython el cual está escrito en C, la implementación estándar y más común de Python.
Capa 2: Python Memory Manager (pymalloc)
- Es un allocator optimizado específico de Python que recicla bloques de memoria para evitar solicitudes repetidas al sistema operativo.
- Destinado a objetos pequeños (≤ 512 bytes) como por ejemplo ints, strings cortos y tuplas pequeñas.
- Trabaja organizado en pools (memoria pre-dividida en bloques iguales) y arenas (contenedores grandes de 1 MB en 64-bit).
- Por ejemplo:
- Python pide arenas al SO con
malloc(1 MB). - Divide cada arena en pools de 4 KB (hasta aproximadamente 256 pools por arena, menos algunos bytes de overhead).
- Una vez pedido el espacio, lo reutiliza indefinidamente.
- Python pide arenas al SO con
- Por ejemplo:
- Reduce fragmentación al usar bloques de tamaño fijo para que no queden partes de memoria inutilizables.
¿Por qué se fragmenta la memoria con malloc normal?
Cuando asignas y liberas objetos de diferentes tamaños, quedan “espacios” que pueden ser muy pequeños para reutilizar:
Malloc normal (fragmentado):
[10][LIBRE-5][20][LIBRE-3][15][LIBRE-8][25]
^ ^ ^
Espacios inútiles: ninguno sirve para un objeto de 20 bytes
Pymalloc (sin fragmentación):
Pool de 32 bytes:
[32][LIBRE-32][32][LIBRE-32][32]
^ ^
Ambos espacios sirven para cualquier objeto ≤ 32 bytes
- Mejora el rendimiento siendo más rápido que malloc ya que realiza menos syscalls o llamadas al sistema operativo. Tiene un mejor uso de caché o menos acceso a la RAM.
Capa 3: Object Allocator
- Capa de alto nivel para tipos específicos de tamaño pequeño.
- Cachés especializados para objetos frecuentes o comunes en lugar de crearlos y destruirlos constantemente.
¿Por qué Python usa malloc/free directamente para objetos grandes?
1. El overhead de pymalloc genera costos adicionales
Para objetos grandes, las optimizaciones de pymalloc no aportan beneficios y solo añaden complejidad innecesaria.
import sys
# Objeto pequeño (< 512 bytes)
pequeño = [1, 2, 3] # ~100 bytes
print(f"Tamaño: {sys.getsizeof(pequeño)} bytes")
# Python usa: pymalloc (Capa 2)
# Objeto grande (> 512 bytes)
grande = [i for i in range(1000)] # ~8000 bytes
print(f"Tamaño: {sys.getsizeof(grande)} bytes")
# Python usa: malloc del SO (Capa 1)
sys.getsizeof(): Esta función retorna el tamaño del contenedor, NO incluye el tamaño de los objetos contenidos ya que lo que se almacena es la referencia o el puntero (8 bytes) al objeto.
Los objetos reales viven independientemente en el heap de memoria (una gran región de memoria dinámica)
Pymalloc tiene un costo de gestión:
Headers de pools
Cada pool de 4 KB necesita guardar información administrativa:
- ¿Cuántos bloques están libres?
- ¿De qué tamaño son los bloques? (16, 32, 48…)
- Punteros al siguiente pool.
Costo: Aproximadamente 32-48 bytes por pool según la versión de Python (overhead fijo independiente del contenido)
Metadata de arenas
Cada arena de 1 MB necesita rastrear:
- ¿Qué pools contiene?
- ¿Cuáles están llenos, parcialmente usados o vacíos?
- Dirección de memoria base.
Costo: ~100-200 bytes por arena
Lógica de búsqueda de bloques
Cuando pides memoria, Python debe:
- Calcular el tamaño de la clase (¿es un objeto de 88 bytes? → usar clase 5 de 96 bytes)
- Buscar un pool con bloques libres de ese tamaño.
- Si no existe, crear nuevo pool.
- Actualizar contadores.
Costo: Ciclos de CPU extra (tiempo de procesamiento)
Cuantización (redondeo)
Si pides 88 bytes, pymalloc te da 96 bytes (el tamaño de clase más cercano).
Por ejemplo:
Pides 88 bytes → redondea a 96 bytes
Pides 1 byte → redondea a 16 bytes
Pides 50 bytes → redondea a 64 bytes
Costo: 8 bytes desperdiciados (9% del objeto)
Clases de tamaño
Las clases de tamaño varían según la versión de Python y arquitectura:
- Python < 3.8 o 32-bit: 8, 16, 24, 32, 40, 48, …, 512 bytes (64 clases)
- Python 3.8+ en 64-bit: 16, 32, 48, 64, 80, 96, …, 512 bytes (32 clases)
- Cada pool contiene bloques de un solo tamaño.
Para validar las clases existentes en tu sistema, ejecuta el siguiente código:
import sys
sys._debugmallocstats()
2. Los objetos grandes son menos frecuentes
La mayoría de objetos en Python son pequeños (enteros, referencias, strings cortos)
import sys
# La mayoría de objetos en Python son pequeños
objetos_pequeños = [
42, # int: ~28 bytes
"hola", # str: ~54 bytes
(1, 2), # tuple: ~56 bytes
{"a": 1}, # dict pequeño: ~232 bytes
]
for obj in objetos_pequeños:
print(f"{type(obj).__name__:10} → {sys.getsizeof(obj):4} bytes")
print("\nObjetos grandes:")
# Estos van directo al SO
grandes = [
"x" * 1000, # str grande
list(range(1000)), # lista grande
{i: i for i in range(100)} # dict grande
]
for obj in grandes:
print(f"{type(obj).__name__:10} → {sys.getsizeof(obj):6} bytes")
Conteo de Referencias (Reference Counting)
El mecanismo principal de gestión de memoria en Python es el conteo de referencias. Cada objeto en Python tiene un campo ob_refcount que lleva cuenta de cuántas referencias apuntan a ese objeto.
Cómo funciona
import sys
# Crear un objeto
a = [1, 2, 3]
print(sys.getrefcount(a)) # Mínimo 2: variable 'a' + argumento temporal
# Puede ser mayor debido a referencias internas del intérprete
# Crear otra referencia
b = a
print(sys.getrefcount(a)) # Se incrementa (ahora tenemos 'a', 'b' + argumento)
# Eliminar una referencia
del b
print(sys.getrefcount(a)) # Decrementa de vuelta
# Cuando el conteo llega a 0, el objeto se libera inmediatamente
del a # Ahora el objeto es liberado
Ventajas del conteo de referencias
- Liberación inmediata de memoria cuando refcount = 0
- Determinístico: sabes exactamente cuándo se libera la memoria
- Permite implementar
__del__()de forma predecible
Desventajas
- Overhead de memoria: cada objeto necesita espacio para el contador
- Overhead de procesamiento: incrementar/decrementar en cada asignación
- No detecta ciclos de referencias (problema fundamental)
El Problema de los Ciclos de Referencias
El conteo de referencias tiene una debilidad fundamental: no puede detectar ciclos.
# Crear un ciclo
class Nodo:
def __init__(self, valor):
self.valor = valor
self.siguiente = None
# Crear un ciclo
nodo1 = Nodo(1)
nodo2 = Nodo(2)
nodo1.siguiente = nodo2
nodo2.siguiente = nodo1 # Ciclo
# Incluso si eliminamos las referencias externas:
del nodo1, nodo2
# Los objetos siguen teniendo refcount > 0 porque se referencian mutuamente
# Sin recolector de basura, esto sería un memory leak o memoria que nunca se libera
Recolector de Basura (Garbage Collector)
Para solucionar el problema de los ciclos, Python implementa un recolector de basura generacional que complementa el conteo de referencias.
Algoritmo Generacional
Python divide los objetos en tres generaciones basándose en la hipótesis de que:
- La mayoría de objetos mueren jóvenes.
- Objetos que sobreviven múltiples colecciones probablemente vivirán mucho tiempo
import gc
# Ver información sobre generaciones
print(gc.get_count()) # (contador_gen0, contador_gen1, contador_gen2)
print(gc.get_threshold()) # Umbrales para activar colección (700, 10, 10)
# Generación 0: objetos nuevos
# Generación 1: objetos que sobrevivieron 1+ colecciones
# Generación 2: objetos que sobrevivieron múltiples colecciones
Funcionamiento del GC
- Generación 0 (más joven):
- Se revisa frecuentemente.
- Umbral típico: ~700 asignaciones netas.
- Colecciones muy rápidas.
- Generación 1 (media):
- Se revisa menos frecuentemente.
- Se ejecuta después de 10 colecciones de generación 0.
- Generación 2 (más vieja):
- Se revisa raramente.
- Se ejecuta después de 10 colecciones de generación 1.
- Contiene objetos de larga vida (por ejemplo, módulos u objetos globales).
Cuándo se ejecuta el GC
import gc
# El GC se ejecuta automáticamente cuando:
# 1. Se excede el umbral de objetos en gen0 (~700 objetos nuevos)
# 2. Se llama explícitamente gc.collect()
# 3. El programa termina
# Ver umbrales actuales
print(gc.get_threshold()) # (700, 10, 10) -> en mi caso da (2000, 10, 10)
# gen0: 700 asignaciones
# gen1: después de 10 colecciones de gen0
# gen2: después de 10 colecciones de gen1
# Modificar umbrales (no recomendado sin profiling previo)
gc.set_threshold(800, 15, 15)
Detección de Ciclos
El GC usa un algoritmo de marcado y barrido (mark and sweep) que, en esencia, consiste en marcar lo que sigue siendo accesible y eliminar lo que no fue marcado:
# El GC puede detectar este ciclo:
class ContenedorCircular:
def __init__(self):
self.referencia = self # Referencia circular
obj = ContenedorCircular()
del obj # GC lo limpiará en la próxima colección
Advertencia sobre __del__(): Si una clase en un ciclo de referencias tiene un método __del__() personalizado, el GC no puede recolectar el ciclo de forma segura. Esto puede causar memory leaks.
class ProblematicaGC:
def __init__(self):
self.referencia = self
def __del__(self):
print("Siendo destruido")
# Si está en un ciclo de referencias,
# el GC no puede recolectar el ciclo si tiene __del__()
Control Manual del GC
import gc
# Deshabilitar GC (útil para debugging o evitar pausas)
gc.disable()
# Ejecutar colección manualmente
n_collected = gc.collect() # Fuerza colección completa
print(f"Objetos liberados: {n_collected}")
# Habilitar de nuevo
gc.enable()
# Ver objetos que el GC está rastreando
print(f"Objetos rastreados: {len(gc.get_objects())}") # Miles de objetos
# Encontrar referencias a un objeto
obj = [1, 2, 3]
refs = gc.get_referrers(obj)
print(f"Referencias a obj: {refs}")
Object Interning y Caching
Python optimiza el uso de memoria reutilizando instancias de objetos inmutables comunes mediante una técnica llamada interning.
En CPython, los enteros en el rango de −5 a 256 se crean una sola vez y se reutilizan durante toda la ejecución del programa:
# Pequeños enteros están pre-creados (singleton)
a = 256
b = 256
print(a is b) # True - mismo objeto en memoria
a = 257
b = 257
print(a is b) # False en CPython (pero puede variar según contexto)
# String interning para identificadores
s1 = "hola"
s2 = "hola"
print(s1 is s2) # True - strings simples se internalizan
Nota sobre string interning: Esto es una optimización de CPython, no garantizada por el lenguaje. Funciona para:
- Literales de string en el código.
- Strings que parecen identificadores (solo letras, números, underscore)
- No funciona siempre para strings creados en runtime.
# Interning explícito
import sys
s1 = sys.intern("cadena larga que queremos reutilizar")
s2 = sys.intern("cadena larga que queremos reutilizar")
print(s1 is s2) # True - ahora comparten memoria
Objetos pre-creados en CPython
- Enteros de -5 a 256 (singleton)
- Empty tuple
() - Strings de un solo carácter
True,False,None
Weak References
En Python, para evitar que ciertos objetos se mantengan vivos innecesariamente, se pueden usar referencias débiles (weak references).
Estas referencias permiten acceder a un objeto sin aumentar su contador de referencias, de modo que el recolector de basura puede eliminarlo cuando ya no haya referencias fuertes:
import weakref
class Pesado:
def __init__(self, datos):
self.datos = datos
# Referencia normal (fuerte)
obj = Pesado([1] * 1000000)
# Referencia débil (no incrementa refcount)
ref_debil = weakref.ref(obj)
print(ref_debil()) # Acceder al objeto: <__main__.Pesado object>
# El objeto sigue vivo
del obj
print(ref_debil()) # None - objeto fue liberado
# Diccionarios débiles
cache = weakref.WeakValueDictionary()
obj = Pesado([1, 2, 3])
cache['key'] = obj
print('key' in cache) # True
del obj
print('key' in cache) # False - se eliminó automáticamente
Casos de uso para weak references
- Cachés que no deben mantener objetos vivos artificialmente; los objetos se eliminan automáticamente cuando ya no existen referencias fuertes.
- Listeners u observers que no deben impedir la recolección de basura del objeto observado.
- Almacenamiento de metadatos asociados a objetos sin extender su tiempo de vida (lifetime).
Context Managers y Gestión de Recursos
Para recursos externos (archivos, conexiones), Python usa context managers:
# Sin context manager (propenso a leaks)
archivo = open('datos.txt')
datos = archivo.read()
archivo.close() # ¿Qué pasa si hay excepción antes?
# Con context manager (seguro)
with open('datos.txt') as archivo:
datos = archivo.read()
# archivo.close() se llama automáticamente, incluso con excepciones
# Context manager personalizado
class GestorRecurso:
def __enter__(self):
print("Adquiriendo recurso")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Liberando recurso")
return False # Re-raise excepciones
with GestorRecurso() as recurso:
print("Usando recurso")
Memory Profiling y Debugging
En Python, el manejo de memoria puede volverse complejo en aplicaciones grandes o de alto rendimiento. Para identificar consumo excesivo, fugas de memoria o objetos que permanecen vivos inesperadamente, existen varias herramientas y técnicas de memory profiling y depuración de referencias.
1. tracemalloc (stdlib)
Permite medir el uso actual y pico de memoria, y rastrear qué líneas
import tracemalloc
tracemalloc.start()
# Código que queremos analizar
lista = [i for i in range(1000000)]
current, peak = tracemalloc.get_traced_memory()
print(f"Uso actual: {current / 1024 / 1024:.2f} MB")
print(f"Peak: {peak / 1024 / 1024:.2f} MB")
# Ver top allocations
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
for stat in top_stats[:3]:
print(stat)
tracemalloc.stop()
2. Memory_profiler (librería externa)
Proporciona reportes línea por línea del uso de memoria de funciones específicas, ideal para analizar el impacto de bloques de código concretos.
from memory_profiler import profile
@profile
def funcion_pesada():
lista = [i**2 for i in range(100000)]
return sum(lista)
funcion_pesada()
# Genera reporte línea por línea de uso de memoria
3. objgraph (visualizar referencias – librería externa)
Visualiza referencias y dependencias entre objetos, ayudando a detectar fugas de memoria y entender por qué ciertos objetos siguen vivos.
import objgraph
class MiClase:
pass
x = MiClase()
y = [x]
# Ver referencias a un objeto
objgraph.show_refs([x], filename='refs.png')
# Ver backreferences (qué mantiene vivo al objeto)
objgraph.show_backrefs([x], filename='backrefs.png')
# Encontrar memory leaks
objgraph.show_most_common_types(limit=10)
Mejores Prácticas y Optimizaciones
1. Liberar referencias grandes explícitamente
import numpy as np
# Array grande
datos = np.zeros((10000, 10000))
# Procesar...
resultado = procesar(datos)
# Liberar explícitamente
del datos # Recomendado para objetos muy grandes
2. Usar generadores para secuencias grandes
# Malo: crea lista completa en memoria
suma = sum([i**2 for i in range(10000000)])
# [i**2 for i in range(10000000)] -> esto es una lista por comprensión.
# Python crea toda la lista en memoria de una vez, con 10 millones de elementos.
# Cada elemento ocupa espacio en memoria (y la lista tiene overhead adicional).
# Esto puede consumir gigabytes de RAM si la secuencia es muy grande.
# Bueno: generator no almacena todo en memoria
suma = sum(i**2 for i in range(10000000))
# i**2 for i in range(10000000)-> esto es un generator expression.
# Python no crea una lista completa, sino que genera cada elemento sobre la marcha.
# sum() toma un elemento a la vez, lo suma, y luego descarta ese elemento.
# Esto mantiene el uso de memoria constante, sin importar cuán grande sea la secuencia.
3. Reutilizar objetos en lugar de crear nuevos
# Malo: crea muchos strings temporales
resultado = ""
for i in range(1000):
resultado += str(i) # Crea nuevo string cada vez
# Bueno: usa lista y join
partes = []
for i in range(1000):
partes.append(str(i))
resultado = "".join(partes)
4. Usar __slots__ para clases con muchas instancias
# Sin slots: cada instancia tiene un __dict__
class Punto:
def __init__(self, x, y):
self.x = x
self.y = y
# Con slots: menor uso de memoria (40-50% menos), elimina el __dict__ por instancia
class PuntoOptimizado:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
# Para millones de instancias, esto ahorra gigabytes
puntos = [PuntoOptimizado(i, i) for i in range(1000000)]
5. Considerar numpy para datos numéricos
import numpy as np
# Lista Python: 8 bytes por entero + overhead
lista = [i for i in range(1000000)]
# Array numpy: 4 bytes por entero, contiguos
array = np.arange(1000000, dtype=np.int32)
# Uso de memoria: ~1/4 de la lista Python
Limitaciones y Consideraciones
Global Interpreter Lock (GIL)
El Global Interpreter Lock (GIL) es un mecanismo que garantiza que solo un thread de Python ejecute bytecode a la vez, incluso en sistemas con múltiples núcleos.
Esto se hace para proteger estructuras internas críticas, principalmente el conteo de referencias, evitando errores de memoria y condiciones de carrera.
- Limita el rendimiento de programas CPU-bound (procesamiento intensivo en CPU) que usan múltiples hilos, ya que no pueden ejecutar bytecode (es lo que ejecuta la máquina virtual de Python) de manera concurrente.
- No afecta tanto programas I/O-bound (espera de recursos), donde los hilos pueden alternar mientras esperan operaciones de red, disco u otros recursos externos.
- A partir de Python 3.13, existen builds con “free-threading” (PEP 703) que permiten ejecutar bytecode en múltiples threads simultáneamente, eliminando esta limitación y permitiendo true multithreading en Python.
Memory Leaks posibles
- Ciclos con
__del__()personalizado. - Referencias en excepciones no manejadas.
- Closures que capturan contexto grande.
- Variables globales que crecen indefinidamente.
# Memory leak por closure
def crear_closure():
datos_grandes = [0] * 1000000
def funcion():
# Captura datos_grandes porque lo referencia
print(len(datos_grandes))
return 42
return funcion
# datos_grandes permanece en memoria mientras exista funcion
f = crear_closure()
Nota: Python solo captura variables en closures que realmente se referencian. Si una función anidada no usa una variable del scope externo, no la captura.
Comparación con Otros Lenguajes
El manejo de memoria en Python se diferencia significativamente de otros lenguajes:
Python vs C/C++
- C/C++: Memoria gestionada manualmente con
malloc/freeonew/delete. Esto permite alta velocidad, pero es propenso a errores como fugas de memoria o punteros colgantes (dirección de memoria que ya no es válida). - Python: Memoria gestionada automáticamente mediante reference counting más un GC generacional para ciclos. Esto reduce errores y simplifica el desarrollo, aunque introduce overhead y cierta pérdida de velocidad.
Python vs Java
- Java: Solo GC generacional puro. Menor overhead por objeto y sin necesidad de contar referencias, pero puede generar pausas más largas durante la recolección de basura (stop-the-world).
- Python: Refcounting + GC generacional. Generalmente produce pausas más cortas y predecibles, pero con más overhead de memoria por objeto.
Python vs Go
- Go: GC concurrente que permite ejecutar múltiples goroutines mientras la recolección ocurre, ideal para programas altamente concurrentes.
- Python: GC stop-the-world combinado con refcounting. Más simple y predecible, pero las pausas pueden ser visibles en programas CPU-bound con varios threads.
Cuando se habla de pausas cortas o largas en el contexto de recolección de basura (GC), se refiere al tiempo durante el cual la ejecución normal del programa se detiene para que el GC pueda limpiar la memoria. Esto se conoce como “stop-the-world”.
Conclusión
La gestión automática de memoria en Python ofrece un equilibrio entre rendimiento y facilidad de uso, permitiendo a los desarrolladores concentrarse en la lógica del negocio sin preocuparse por la liberación manual de memoria.
El sistema híbrido de reference counting combinado con un garbage collector generacional proporciona varias ventajas clave:
- Liberación inmediata para la mayoría de los objetos sin ciclos, garantizando eficiencia en el uso de memoria.
- Detección y recolección de ciclos para casos más complejos, evitando fugas de memoria.
- Optimizaciones específicas, como
pymallocy object interning, que mejoran el rendimiento y reducen el overhead en los escenarios más comunes.
Contras / Limitaciones
- Overhead de memoria: Cada objeto mantiene un contador de referencias, lo que aumenta ligeramente el consumo de memoria.
- Pausas visibles en GC: Aunque cortas, la recolección de ciclos puede generar pausas perceptibles en programas CPU-bound grandes.
- Limitaciones en multithreading: El GIL impide el “true parallelism” de hilos que ejecutan bytecode, limitando la eficiencia en programas altamente concurrentes.
Referencias
Documentación Oficial de Python
- PEP 445 – Memory Allocation API – Arquitectura de gestión de memoria, Capas 1 y 2.
- Python C API – Memory Management – PyMalloc, clases de tamaño, overhead.
- Python C API – Integer Objects – Object interning (enteros -5 a 256)
- PEP 703 – Making the GIL Optional – Global Interpreter Lock, Python 3.13+ free-threading.
- gc module – Garbage Collector – Recolector de basura, gc.collect(), gc.get_threshold().
- weakref module – Weak References – Referencias débiles.
- tracemalloc module – Memory Profiling – Memory profiling y debugging.
- Introduction Python– Introducción Python.
Código Fuente CPython
- Objects/obmalloc.c – Implementación de pymalloc – Clases de tamaño, implementación de pymalloc, ALIGNMENT.
- Issue #80799 – clang expects 16-byte alignment – Cambio a 16 bytes en Python 3.8+
- Commit f0be4bb – pymalloc alignment fix – Evidencia del cambio de alineación.
Artículos Técnicos
- Memory Management in Python – Artem Golubin – Explicación detallada de pools, arenas y bloques en pymalloc.
- https://cs-fundamentals.com/c-programming/memory-layout-of-c-program-code-data-segments – Segmentos de memoria por ejemplo Heap y Stack.
String Interning
- CPython InternalDocs – String Interning – Documentación de CPython sobre string interning.
- Guide to String Interning in Python – Stack Abuse – Explicación de cuándo ocurre interning automático.
- The Internals of Python String Interning – Análisis del código fuente de CPython.
- String Interning Discovery – Función
all_name_chars()y reglas de interning.
Garbage Collector
- Java Garbage Collector – Java Garbage Collector.
- Go Memory Management – Go Memoria Management.