Pérdida de memoria

format_list_bulleted Contenido keyboard_arrow_down
ImprimirCitar
Cuando un programa informático no libera memoria innecesaria

En informática, una pérdida de memoria es un tipo de fuga de recursos que ocurre cuando un programa de computadora administra incorrectamente las asignaciones de memoria de manera que la memoria que ya no se necesita no se libera. Una pérdida de memoria también puede ocurrir cuando un objeto está almacenado en la memoria pero el código en ejecución no puede acceder a él (es decir, memoria inalcanzable). Una fuga de memoria tiene síntomas similares a otros problemas y, por lo general, solo puede ser diagnosticada por un programador con acceso al código fuente del programa.

Un concepto relacionado es la "fuga de espacio", que ocurre cuando un programa consume demasiada memoria pero finalmente la libera.

Debido a que pueden agotar la memoria disponible del sistema a medida que se ejecuta una aplicación, las fugas de memoria suelen ser la causa o un factor que contribuye al envejecimiento del software.

Consecuencias

Una fuga de memoria reduce el rendimiento de la computadora al reducir la cantidad de memoria disponible. Eventualmente, en el peor de los casos, se puede asignar demasiada memoria disponible y todo o parte del sistema o dispositivo deja de funcionar correctamente, la aplicación falla o el sistema se ralentiza enormemente debido a la hiperpaginación.

Las fugas de memoria pueden no ser graves o ni siquiera detectables por medios normales. En los sistemas operativos modernos, la memoria normal utilizada por una aplicación se libera cuando la aplicación finaliza. Esto significa que una fuga de memoria en un programa que solo se ejecuta durante un breve período de tiempo puede pasar inadvertida y rara vez es grave.

Las filtraciones mucho más graves incluyen aquellas:

  • donde un programa funciona durante mucho tiempo y consume memoria agregada a lo largo del tiempo, como tareas de fondo en servidores, y especialmente en sistemas integrados que pueden ser dejados correr durante muchos años
  • donde la nueva memoria se asigna con frecuencia para tareas de una sola vez, como al renderizar los marcos de un juego de computadora o vídeo animado
  • donde un programa puede solicitar la memoria, como la memoria compartida, que no se libera, incluso cuando el programa termina
  • donde la memoria es muy limitada, como en un sistema incrustado o dispositivo portátil, o donde el programa requiere una cantidad muy grande de memoria para comenzar, dejando poco margen para las filtraciones
  • donde se produce una fuga dentro del sistema operativo o administrador de memoria
  • cuando un controlador de dispositivo del sistema causa una fuga
  • se ejecuta en un sistema operativo que no libera automáticamente la memoria en la terminación del programa.

Un ejemplo de pérdida de memoria

El siguiente ejemplo, escrito en pseudocódigo, pretende mostrar cómo se puede producir una fuga de memoria y sus efectos, sin necesidad de conocimientos de programación. El programa en este caso es parte de un software muy simple diseñado para controlar un ascensor. Esta parte del programa se ejecuta cada vez que alguien dentro del ascensor presiona el botón de un piso.

Cuando se pulsa un botón:
Consigue un poco de memoria, que se utilizará para recordar el número del piso
Ponga el número de piso en la memoria
¿Ya estamos en el suelo objetivo?
Si es así, no tenemos nada que hacer: terminado
De lo contrario:
Espera hasta que el ascensor esté vacío
Vaya al piso requerido
Libera la memoria que solíamos recordar el número del piso

La fuga de memoria ocurriría si el número de piso solicitado es el mismo piso en el que se encuentra el ascensor; se omitiría la condición para liberar la memoria. Cada vez que ocurre este caso, se pierde más memoria.

Casos como este no suelen tener efectos inmediatos. Las personas no suelen presionar el botón del piso en el que ya están y, en cualquier caso, el ascensor puede tener suficiente memoria libre para que esto suceda cientos o miles de veces. Sin embargo, el ascensor eventualmente se quedará sin memoria. Esto podría llevar meses o años, por lo que es posible que no se descubra a pesar de las pruebas exhaustivas.

Las consecuencias serían desagradables; como mínimo, el ascensor dejaría de responder a las solicitudes para pasar a otro piso (como cuando se intenta llamar al ascensor o cuando alguien está dentro y presiona los botones del piso). Si otras partes del programa necesitan memoria (una parte asignada para abrir y cerrar la puerta, por ejemplo), entonces nadie podrá entrar, y si alguien está dentro, quedará atrapado (suponiendo que las puertas no se puedan abrir). abierto manualmente).

La pérdida de memoria dura hasta que se reinicia el sistema. Por ejemplo: si la energía del ascensor se apagara o se produjera un corte de energía, el programa dejaría de funcionar. Cuando se volvía a encender, el programa se reiniciaba y toda la memoria volvía a estar disponible, pero el lento proceso de pérdida de memoria se reiniciaba junto con el programa, perjudicando eventualmente el correcto funcionamiento del sistema.

La fuga en el ejemplo anterior se puede corregir trayendo el 'lanzamiento' operación fuera del condicional:

Cuando se pulsa un botón:
Consigue un poco de memoria, que se utilizará para recordar el número del piso
Ponga el número de piso en la memoria
¿Ya estamos en el suelo objetivo?
Si no:
Espera hasta que el ascensor esté vacío
Vaya al piso requerido
Libera la memoria que solíamos recordar el número del piso

Problemas de programación

Las fugas de memoria son un error común en la programación, especialmente cuando se usan lenguajes que no tienen una recolección de basura automática integrada, como C y C++. Normalmente, se produce una pérdida de memoria porque la memoria asignada dinámicamente se ha vuelto inaccesible. La prevalencia de los errores de pérdida de memoria ha llevado al desarrollo de una serie de herramientas de depuración para detectar la memoria inaccesible. BoundsChecker, Deleaker, Validador de memoria, IBM Rational Purify, Valgrind, Parasoft Insure++, Dr. Memory y memwatch son algunos de los depuradores de memoria más populares para programas C y C++. "Conservador" Las capacidades de recolección de basura se pueden agregar a cualquier lenguaje de programación que carezca de ella como una función integrada, y las bibliotecas para hacer esto están disponibles para programas C y C ++. Un coleccionista conservador encuentra y recupera la mayoría, pero no toda, la memoria inalcanzable.

Aunque el administrador de memoria puede recuperar la memoria inalcanzable, no puede liberar la memoria que aún es accesible y, por lo tanto, potencialmente aún útil. Por lo tanto, los administradores de memoria modernos brindan técnicas para que los programadores marquen semánticamente la memoria con diferentes niveles de utilidad, que corresponden a diferentes niveles de accesibilidad. El administrador de memoria no libera un objeto que es fuertemente alcanzable. Un objeto es fuertemente alcanzable si es alcanzable directamente por una referencia fuerte o indirectamente por una cadena de referencias fuertes. (Una referencia fuerte es una referencia que, a diferencia de una referencia débil, evita que un objeto se recopile como basura). Para evitar esto, el desarrollador es responsable de limpiar las referencias después de su uso, normalmente configurando la para anular una vez que ya no sea necesario y, si es necesario, anulando el registro de los detectores de eventos que mantienen fuertes referencias al objeto.

En general, la gestión automática de la memoria es más robusta y cómoda para los desarrolladores, ya que no necesitan implementar rutinas de liberación ni preocuparse por la secuencia en la que se realiza la limpieza ni por si un objeto todavía está o no. referenciado. Es más fácil para un programador saber cuándo ya no se necesita una referencia que saber cuándo ya no se hace referencia a un objeto. Sin embargo, la gestión automática de la memoria puede imponer una sobrecarga de rendimiento y no elimina todos los errores de programación que provocan fugas de memoria.

RAII

RAII, abreviatura de Resource Acquisition Is Initialization, es un enfoque del problema que comúnmente se toma en C++, D y Ada. Implica asociar objetos con ámbito con los recursos adquiridos y liberar automáticamente los recursos una vez que los objetos están fuera del ámbito. A diferencia de la recolección de basura, RAII tiene la ventaja de saber cuándo existen objetos y cuándo no. Compare los siguientes ejemplos de C y C++:

/* C versión */#include ■stdlib.hvacío f()int n){} int* array = calloc()n, tamaño()int)); do_some_work()array); gratis()array);}
// Versión C++#include Identificadorvacío f()int n){} std::vector.int array ()n); do_some_work()array);}

La versión C, tal como se implementa en el ejemplo, requiere desasignación explícita; la matriz se asigna dinámicamente (desde el montón en la mayoría de las implementaciones de C) y continúa existiendo hasta que se libera explícitamente.

La versión de C++ no requiere desasignación explícita; siempre ocurrirá automáticamente tan pronto como el objeto array quede fuera del alcance, incluso si se lanza una excepción. Esto evita algunos de los gastos generales de los esquemas de recolección de basura. Y debido a que los destructores de objetos pueden liberar otros recursos además de la memoria, RAII ayuda a evitar la fuga de recursos de entrada y salida a los que se accede a través de un identificador, que la recolección de basura de marcar y barrer no maneja correctamente. Estos incluyen archivos abiertos, ventanas abiertas, notificaciones de usuario, objetos en una biblioteca de dibujo de gráficos, primitivos de sincronización de subprocesos como secciones críticas, conexiones de red y conexiones al Registro de Windows u otra base de datos.

Sin embargo, usar RAII correctamente no siempre es fácil y tiene sus propias trampas. Por ejemplo, si uno no tiene cuidado, es posible crear punteros colgantes (o referencias) al devolver datos por referencia, solo para que esos datos se eliminen cuando el objeto que los contiene queda fuera del alcance.

D utiliza una combinación de RAII y recolección de elementos no utilizados, empleando la destrucción automática cuando está claro que no se puede acceder a un objeto fuera de su alcance original y la recolección de elementos no utilizados en caso contrario.

Conteo de referencias y referencias cíclicas

Los esquemas de recolección de basura más modernos a menudo se basan en una noción de accesibilidad: si no tiene una referencia utilizable a la memoria en cuestión, se puede recolectar. Otros esquemas de recolección de basura pueden basarse en el conteo de referencias, donde un objeto es responsable de realizar un seguimiento de cuántas referencias apuntan a él. Si el número desciende a cero, se espera que el objeto se libere y permita recuperar su memoria. El defecto de este modelo es que no hace frente a las referencias cíclicas, y es por eso que hoy en día la mayoría de los programadores están preparados para aceptar la carga de los sistemas más costosos de marca y barrido.

El siguiente código de Visual Basic ilustra la pérdida de memoria canónica de recuento de referencias:

Dim A, BSet A = Crear objeto()"Algunos.)Set B = Crear objeto()"Algunos.)' En este punto, los dos objetos cada uno tiene una referencia,Set A.miembro = BSet B.miembro = A' Ahora cada uno tiene dos referencias.Set A = Nada ' Podrías salir de ella...Set B = Nada ' ¡Y ahora tienes una fuga de memoria!Final

En la práctica, este ejemplo trivial se detectaría de inmediato y se arreglaría. En la mayoría de los ejemplos reales, el ciclo de referencias abarca más de dos objetos y es más difícil de detectar.

Un ejemplo bien conocido de este tipo de fuga saltó a la fama con el surgimiento de las técnicas de programación AJAX en los navegadores web en el problema del oyente caducado. El código JavaScript que asoció un elemento DOM con un controlador de eventos y no eliminó la referencia antes de salir, perdería memoria (las páginas web de AJAX mantienen vivo un DOM determinado durante mucho más tiempo que las páginas web tradicionales, por lo que esta fuga fue mucho más evidente).

Efectos

Si un programa tiene una pérdida de memoria y su uso de memoria aumenta constantemente, por lo general no habrá un síntoma inmediato. Cada sistema físico tiene una cantidad finita de memoria, y si la fuga de memoria no se contiene (por ejemplo, reiniciando el programa de fuga) eventualmente causará problemas.

La mayoría de los sistemas operativos de escritorio de consumo modernos tienen memoria principal que está alojada físicamente en microchips RAM y almacenamiento secundario, como un disco duro. La asignación de memoria es dinámica: cada proceso obtiene tanta memoria como solicita. Las páginas activas se transfieren a la memoria principal para un acceso rápido; las páginas inactivas se envían al almacenamiento secundario para hacer espacio, según sea necesario. Cuando un solo proceso comienza a consumir una gran cantidad de memoria, generalmente ocupa más y más memoria principal, empujando otros programas al almacenamiento secundario, lo que generalmente ralentiza significativamente el rendimiento del sistema. Incluso si el programa con fugas finaliza, es posible que otros programas tarden un tiempo en volver a la memoria principal y que el rendimiento vuelva a la normalidad.

Cuando se agota toda la memoria de un sistema (ya sea memoria virtual o solo memoria principal, como en un sistema integrado), cualquier intento de asignar más memoria fallará. Esto generalmente hace que el programa que intenta asignar la memoria termine o genere una falla de segmentación. Algunos programas están diseñados para recuperarse de esta situación (posiblemente recurriendo a la memoria reservada previamente). El primer programa en experimentar la falta de memoria puede o no ser el programa que tiene la pérdida de memoria.

Algunos sistemas operativos multitarea tienen mecanismos especiales para lidiar con una condición de falta de memoria, como matar procesos al azar (lo que puede afectar a los procesos 'inocentes'), o matar el proceso más grande en memoria (que presumiblemente es la que causa el problema). Algunos sistemas operativos tienen un límite de memoria por proceso, para evitar que un programa acapare toda la memoria del sistema. La desventaja de este arreglo es que el sistema operativo a veces debe reconfigurarse para permitir el funcionamiento adecuado de los programas que legítimamente requieren grandes cantidades de memoria, como los que se ocupan de gráficos, videos o cálculos científicos.

El patrón "sawtooth" de la utilización de la memoria: la gota repentina en la memoria usada es un síntoma candidato para una fuga de memoria.

Si la fuga de memoria está en el núcleo, es probable que el sistema operativo falle. Las computadoras sin una gestión de memoria sofisticada, como los sistemas integrados, también pueden fallar por completo debido a una pérdida de memoria persistente.

Los sistemas de acceso público, como servidores web o enrutadores, son propensos a sufrir ataques de denegación de servicio si un atacante descubre una secuencia de operaciones que puede desencadenar una fuga. Tal secuencia se conoce como exploit.

Un "diente de sierra" patrón de utilización de la memoria puede ser un indicador de una pérdida de memoria dentro de una aplicación, especialmente si las caídas verticales coinciden con reinicios o reinicios de esa aplicación. Sin embargo, se debe tener cuidado porque los puntos de recolección de basura también podrían causar un patrón de este tipo y mostrarían un uso saludable del montón.

Otros consumidores de memoria

Tenga en cuenta que el aumento constante del uso de la memoria no es necesariamente evidencia de una fuga de memoria. Algunas aplicaciones almacenarán cantidades cada vez mayores de información en la memoria (por ejemplo, como caché). Si la memoria caché puede crecer tanto como para causar problemas, esto puede ser un error de programación o diseño, pero no es una fuga de memoria ya que la información permanece nominalmente en uso. En otros casos, los programas pueden requerir una cantidad excesivamente grande de memoria porque el programador ha asumido que la memoria siempre es suficiente para una tarea en particular; por ejemplo, un procesador de archivos de gráficos podría comenzar leyendo todo el contenido de un archivo de imagen y almacenándolo todo en la memoria, algo que no es viable cuando una imagen muy grande excede la memoria disponible.

Para decirlo de otra manera, una fuga de memoria surge de un tipo particular de error de programación, y sin acceso al código del programa, alguien que ve los síntomas solo puede adivinar que podría haber una fuga de memoria. Sería mejor utilizar términos como "uso de memoria en constante aumento" donde no existe tal conocimiento interno.

Un ejemplo sencillo en C++

El siguiente programa C++ pierde memoria deliberadamente al perder el puntero a la memoria asignada.

int principal() {} int* a = nuevo int()5); a = nullptr; /* El puntero de la 'a' ya no existe, y por lo tanto no puede ser liberado, pero la memoria sigue siendo asignada por el sistema. Si el programa continúa creando tales punteros sin liberarlos,  consumirá la memoria continuamente. Por lo tanto, se produciría una fuga. */}

Contenido relacionado

UCSD Pascual

UCSD Pascal es un sistema de lenguaje de programación Pascal que se ejecuta en el UCSD p-System, un sistema operativo portátil y altamente independiente de...

Protocolo de mensajes de control de Internet

El Protocolo de mensajes de control de Internet es un protocolo de soporte en el conjunto de protocolos de Internet. Los dispositivos de red, incluidos los...

Instituto de Ciencias de la Información

El Instituto de Ciencias de la Información de la USC es un componente de la Escuela de Ingeniería Viterbi de la Universidad del Sur de California y se...
Más resultados...
Tamaño del texto:
Copiar