En informática , la gestión manual de memoria se refiere al uso de instrucciones manuales por parte del programador para identificar y desasignar objetos no utilizados, o basura . Hasta mediados de la década de 1990, la mayoría de los lenguajes de programación utilizados en la industria admitían la gestión manual de memoria, aunque la recolección de basura existe desde 1959, cuando se introdujo con Lisp . Hoy, sin embargo, los lenguajes con recolección de basura como Java son cada vez más populares y los lenguajes Objective-C y Swift proporcionan una funcionalidad similar a través del conteo automático de referencias . Los principales lenguajes administrados manualmente que todavía se usan ampliamente en la actualidad son C y C++ ; consulte Asignación dinámica de memoria en C.
Muchos lenguajes de programación utilizan técnicas manuales para determinar cuándo asignar un nuevo objeto del almacén libre. C utiliza la malloc
función; C++ y Java utilizan el new
operador; y muchos otros lenguajes (como Python) asignan todos los objetos del almacén libre. Determinar cuándo se debe crear un objeto ( creación de objetos ) es generalmente trivial y no problemático, aunque técnicas como los grupos de objetos significan que un objeto puede crearse antes de su uso inmediato. El verdadero desafío es la destrucción de objetos : determinar cuándo un objeto ya no es necesario (es decir, es basura) y organizar que su almacenamiento subyacente se devuelva al almacén libre para su reutilización. En la asignación manual de memoria, esto también lo especifica manualmente el programador; a través de funciones como free()
en C, o el delete
operador en C++; esto contrasta con la destrucción automática de objetos guardados en variables automáticas , en particular las variables locales (no estáticas) de funciones, que se destruyen al final de su alcance en C y C++.
Por ejemplo
Se sabe que la gestión manual de memoria permite varias clases importantes de errores en un programa cuando se utiliza incorrectamente, en particular violaciones de la seguridad de la memoria o fugas de memoria . Estas son una fuente importante de errores de seguridad .
Se sabe que los lenguajes que utilizan exclusivamente la recolección de basura evitan las dos últimas clases de defectos. Aún pueden ocurrir fugas de memoria (y las fugas limitadas ocurren con frecuencia con la recolección de basura generacional o conservadora), pero generalmente son menos graves que las fugas de memoria en los sistemas manuales.
La gestión manual de memoria tiene una ventaja de corrección, que es que permite la gestión automática de recursos a través del paradigma Adquisición de recursos es inicialización (RAII).
Esto ocurre cuando los objetos poseen recursos escasos del sistema (como recursos gráficos, manejadores de archivos o conexiones de bases de datos) que deben ser entregados cuando un objeto es destruido – cuando la vida útil de la propiedad del recurso debe estar vinculada a la vida útil del objeto. Los lenguajes con administración manual pueden organizar esto adquiriendo el recurso durante la inicialización del objeto (en el constructor) y liberándolo durante la destrucción del objeto (en el destructor ), que ocurre en un momento preciso. Esto se conoce como Adquisición de recursos en inicialización.
Esto también se puede utilizar con el recuento de referencias determinista . En C++, esta capacidad se utiliza para automatizar la desasignación de memoria dentro de un marco que de otro modo sería manual; el uso de la shared_ptr
plantilla en la biblioteca estándar del lenguaje para realizar la gestión de memoria es un paradigma común. Sin embargo, noshared_ptr
es adecuado para todos los patrones de uso de objetos.
Este enfoque no se puede utilizar en la mayoría de los lenguajes de recolección de basura (especialmente en el rastreo de recolectores de basura o en el conteo de referencias más avanzado) debido a que la finalización no es determinista y, a veces, ni siquiera ocurre. Es decir, es difícil definir (o determinar) cuándo o si se puede llamar a un método finalizador ; esto se conoce comúnmente como el problema del finalizador. Java y otros lenguajes que implementan un recolector de basura con frecuencia utilizan la gestión manual de los escasos recursos del sistema además de la memoria a través del patrón dispose : se espera que cualquier objeto que administre recursos implemente el dispose()
método, que libera dichos recursos y marca el objeto como inactivo. Se espera que los programadores invoquen dispose()
manualmente según corresponda para evitar la "fuga" de recursos gráficos escasos. Para los recursos de pila (recursos adquiridos y liberados dentro de un solo bloque de código), esto se puede automatizar mediante varias construcciones de lenguaje, como -with-resources de Python with
, C# using
o Java .try
Muchos defensores de la gestión manual de la memoria sostienen que ofrece un rendimiento superior en comparación con las técnicas automáticas, como la recolección de basura . Tradicionalmente, la latencia era la mayor ventaja, pero ya no es así. La asignación manual suele tener una localidad de referencia superior . [ cita requerida ]
También se sabe que la asignación manual es más apropiada para sistemas donde la memoria es un recurso escaso, debido a una recuperación más rápida. Los sistemas de memoria pueden "desestabilizarse" y, de hecho, lo hacen con frecuencia a medida que el tamaño del conjunto de trabajo de un programa se acerca al tamaño de la memoria disponible; los objetos no utilizados en un sistema con recolección de basura permanecen en un estado no recuperado durante más tiempo que en sistemas administrados manualmente, porque no se recuperan inmediatamente, lo que aumenta el tamaño efectivo del conjunto de trabajo.
La gestión manual tiene una serie de desventajas de rendimiento documentadas :
delete
y similares generan una sobrecarga cada vez que se realizan, que se puede amortizar en ciclos de recolección de basura. Esto es especialmente cierto en aplicaciones multiproceso, donde las llamadas de eliminación deben estar sincronizadas.La latencia es un punto de debate que ha cambiado con el tiempo: los primeros recolectores de basura y las implementaciones simples tenían un rendimiento muy deficiente en comparación con la administración manual de memoria, pero los recolectores de basura modernos y sofisticados a menudo funcionan tan bien o mejor que la administración manual de memoria. [ cita requerida ]
La asignación manual no sufre los largos tiempos de "pausa" que ocurren en la recolección de basura simple de "detener el mundo", aunque los recolectores de basura modernos tienen ciclos de recolección que a menudo no son perceptibles. [ cita requerida ]
Tanto la gestión manual de memoria como la recolección de basura sufren de tiempos de desasignación potencialmente ilimitados: la gestión manual de memoria porque desasignar un solo objeto puede requerir desasignar sus miembros y, recursivamente, los miembros de sus miembros, etc., mientras que la recolección de basura puede tener ciclos de recolección largos. Esto es especialmente un problema en sistemas de tiempo real , donde los ciclos de recolección ilimitados son generalmente inaceptables; la recolección de basura en tiempo real es posible pausando el recolector de basura, mientras que la gestión manual de memoria en tiempo real requiere evitar grandes desasignaciones o pausar manualmente la desasignación.