El módulo gc en Python expone la interfaz del recolector de ciclos de CPython. El intérprete ya libera la mayoría de los objetos mediante conteo de referencias, pero ciclos como “el objeto A apunta a B y B vuelve a apuntar a A” pueden permanecer incluso cuando la aplicación perdió todas las referencias externas. El recolector complementario identifica esos grupos inalcanzables e intenta liberarlos.
La mayoría de los programas debería conservar la configuración predeterminada. La API resulta útil para investigar fugas de memoria, servicios de larga duración, servidores prefork, pruebas con estructuras cíclicas y observabilidad de pausas del recolector.
Conteo de referencias y ciclos
Cuando desaparece la última referencia a un objeto común, CPython normalmente lo destruye de inmediato. Un ciclo impide que los contadores lleguen a cero.
class Nodo:
def __init__(self, nombre):
self.nombre = nombre
self.siguiente = None
a = Nodo("a")
b = Nodo("b")
a.siguiente = b
b.siguiente = a
del a, bDespués de del, el programa ya no puede alcanzar los nodos, pero ellos continúan referenciándose. Una recolección cíclica posterior puede eliminarlos.
Comprueba si está activo
import gc
print(gc.isenabled())gc.enable() activa la recolección automática y gc.disable() la desactiva. Deshabilitar el recolector no desactiva el conteo de referencias; solo suspende la búsqueda de ciclos.
No lo desactives sin medir
Una aplicación que demostró no crear ciclos puede desactivar temporalmente el recolector durante una sección sensible a latencia. La restauración debe estar garantizada.
estaba_activo = gc.isenabled()
try:
gc.disable()
ejecutar_seccion_controlada()
finally:
if estaba_activo:
gc.enable()Si alguna dependencia crea ciclos durante ese intervalo, permanecerán hasta una recolección futura. No uses esta técnica como optimización genérica.
Fuerza una recolección
gc.collect() ejecuta una recolección completa por defecto y devuelve la suma de objetos recolectados y no recolectables.
procesados = gc.collect()
print(f"Objetos procesados: {procesados}")Llamadas manuales frecuentes pueden reducir el rendimiento. Úsalas en pruebas, diagnósticos y puntos de ciclo de vida cuidadosamente elegidos, no después de cada petición.
Selecciona una generación
El recolector agrupa objetos según cuántas recolecciones sobreviven. Los objetos nuevos entran en la generación joven y los supervivientes avanzan a generaciones antiguas.
gc.collect(0)
gc.collect(1)
gc.collect(2)El comportamiento de la generación intermedia cambió durante la serie Python 3.14. El código que dependa de detalles generacionales debe probarse en la versión exacta. Las recolecciones completas también limpian varias free lists internas, aunque algunos objetos, como floats, pueden permanecer.
Consulta estadísticas
gc.get_stats() devuelve un diccionario por generación con cantidad de recolecciones, objetos recogidos y objetos no recolectables.
for generacion, datos in enumerate(gc.get_stats()):
print(generacion, datos)Observa tendencias bajo cargas comparables. Una lectura aislada no demuestra una fuga. Relaciona los datos con memoria del proceso y volumen de trabajo.
Consulta contadores y thresholds
print(gc.get_count())
print(gc.get_threshold())get_count() muestra contadores actuales de asignaciones. get_threshold() informa los límites de la heurística automática.
Ajusta thresholds con cuidado
gc.set_threshold() cambia la frecuencia. Establecer el primer límite en cero desactiva la recolección automática.
anteriores = gc.get_threshold()
try:
gc.set_threshold(1000, 15, 15)
ejecutar_carga()
finally:
gc.set_threshold(*anteriores)Valores menores recolectan con mayor frecuencia y añaden overhead. Valores mayores reducen pausas y pueden elevar la memoria. Los builds free-threaded también consideran crecimiento de memoria y asignaciones netas.
Usa callbacks para observabilidad
gc.callbacks contiene funciones llamadas antes y después de cada recolección.
import time
inicios = {}
def observar(fase, info):
generacion = info["generation"]
if fase == "start":
inicios[generacion] = time.perf_counter()
else:
duracion = time.perf_counter() - inicios.pop(generacion, 0)
print(
generacion,
info["collected"],
info["uncollectable"],
duracion,
)
gc.callbacks.append(observar)Los callbacks se ejecutan durante una operación sensible. Deben ser rápidos y evitar red, locks, exceso de logs y muchas asignaciones. Elimínalos al terminar el diagnóstico.
Activa flags de debug
gc.set_debug() habilita información en stderr.
gc.set_debug(gc.DEBUG_STATS)DEBUG_COLLECTABLE y DEBUG_UNCOLLECTABLE imprimen objetos encontrados. La salida puede ser enorme y contener datos sensibles.
DEBUG_LEAK y DEBUG_SAVEALL
DEBUG_LEAK combina varias flags e incluye DEBUG_SAVEALL. En ese modo, los objetos inalcanzables se agregan a gc.garbage en lugar de liberarse.
gc.set_debug(gc.DEBUG_LEAK)
gc.collect()
print(len(gc.garbage))Esto incrementa la memoria intencionalmente. Después de inspeccionar, restaura las flags, limpia la lista y recolecta otra vez.
gc.set_debug(0)
gc.garbage.clear()
gc.collect()Comprende gc.garbage
Desde la PEP 442, los objetos Python con __del__() normalmente siguen siendo recolectables incluso dentro de ciclos. La lista suele estar vacía salvo extensiones C específicas o cuando DEBUG_SAVEALL está activo.
No ignores una lista no vacía. Registra tipos de manera segura sin serializar objetos arbitrarios ni llamar sus métodos.
Comprueba si un objeto está rastreado
gc.is_tracked() informa si participa en la recolección cíclica.
print(gc.is_tracked(10))
print(gc.is_tracked([]))
print(gc.is_tracked({"clave": 1}))Los tipos atómicos normalmente no se rastrean. Algunos contenedores simples pueden quedar fuera como optimización y volver a rastrearse al contener valores complejos.
Lista objetos rastreados
gc.get_objects() devuelve objetos rastreados, opcionalmente de una generación.
objetos = gc.get_objects()
print(len(objetos))La lista puede ser enorme, aumenta temporalmente la memoria y revela estado interno. Úsala en un proceso de diagnóstico, filtra por tipo y no publiques su contenido mediante endpoints administrativos desprotegidos.
Encuentra referenciadores
gc.get_referrers(objetivo) devuelve containers rastreados que apuntan directamente al objetivo.
gc.collect()
referenciadores = gc.get_referrers(objetivo)
for item in referenciadores:
print(type(item))El resultado puede incluir frames del depurador, el propio código de inspección y objetos parcialmente construidos. La documentación recomienda usarlo solo para debugging. Evita llamar métodos de los objetos retornados.
Encuentra referentes
gc.get_referents() devuelve objetos visitados por el protocolo C tp_traverse.
for item in gc.get_referents(objetivo):
print(type(item))No representa necesariamente todas las referencias semánticas. Contiene las que el tipo presenta al recolector para detectar ciclos.
Finalización y resurrección
gc.is_finalized() indica si el finalizador ya se ejecutó. Un __del__() problemático puede resucitar el objeto guardándolo nuevamente.
resucitado = None
class Lazaro:
def __del__(self):
global resucitado
resucitado = self
obj = Lazaro()
del obj
gc.collect()
print(gc.is_finalized(resucitado))Evita lógica compleja en __del__(). Prefiere context managers, close() explícito y weakref.finalize(). Consulta weakref en Python.
Congela antes de fork
gc.freeze() mueve objetos rastreados a una generación permanente ignorada por recolecciones futuras. En servidores que usan fork() sin exec(), puede mejorar el uso de copy-on-write.
gc.disable()
cargar_aplicacion()
gc.freeze()
pid = os.fork()
if pid == 0:
gc.enable()El patrón exige arquitectura deliberada: desactiva temprano en el padre, congela justo antes del fork y reactiva en los hijos. No se aplica a todos los despliegues.
Descongela cuando sea necesario
gc.unfreeze() devuelve objetos permanentes a la generación antigua. gc.get_freeze_count() muestra cuántos están congelados.
print(gc.get_freeze_count())
gc.unfreeze()Descongelar puede provocar una gran recolección posterior. Mide memoria y latencia.
GC y fugas de memoria
No todo crecimiento procede de ciclos. Caches, colas, registros, buffers, módulos, pools y variables globales pueden conservar referencias válidas. El recolector no libera objetos todavía alcanzables.
Usa tracemalloc en Python para comparar snapshots y después inspecciona referencias de tipos sospechosos.
Cuenta objetos por tipo
from collections import Counter
conteo = Counter(type(obj).__name__ for obj in gc.get_objects())
for nombre, total in conteo.most_common(20):
print(nombre, total)Toma snapshots en momentos equivalentes. La propia inspección crea objetos y distorsiona diferencias pequeñas.
Evita ciclos innecesarios
Callbacks, closures, observers, grafos padre-hijo y tareas crean ciclos con facilidad. Usa referencias débiles cuando una relación no representa propiedad. Elimina listeners y tareas al cerrar componentes.
Consulta también la guía sobre fugas de memoria en Python.
No llames collect en cada petición
Una recolección completa por petición suele reducir throughput y aumentar latencia. Si la memoria solo baja tras collect(), investiga por qué se crean tantos ciclos o por qué los thresholds no acompañan la carga.
Seguridad y auditoría
get_objects(), get_referrers() y get_referents() emiten eventos de auditoría y pueden revelar secretos. Limita su uso a administradores, logs protegidos y entornos de diagnóstico.
Buenas prácticas
- Mantén la recolección automática activa por defecto.
- Mide antes de ajustar thresholds.
- Usa callbacks ligeros para métricas.
- Limpia flags y
gc.garbagetras depurar. - Usa referenciadores solo para diagnóstico.
- Prefiere gestión explícita de recursos.
- Usa
tracemallocpara localizar crecimiento. - Prueba en la versión exacta de Python.
Conclusión
gc en Python permite observar y controlar el recolector cíclico que complementa el conteo de referencias de CPython. Estadísticas, callbacks, flags, inspección y congelación ayudan en diagnósticos avanzados.
Usa la interfaz con cuidado porque añade overhead y expone objetos internos. Consulta la documentación oficial de gc y la guía de diseño del garbage collector de CPython.







