Comprender la gestión de memoria en lenguajes de programación modernos

Hola Habr! Les presento la traducción del artículo " Desmitificando la gestión de memoria en lenguajes de programación modernos " por Deepu K Sasidharan.

En esta serie de artículos, me gustaría disipar el velo de misticismo sobre la gestión de la memoria en el software (en lo sucesivo denominado software) y considerar en detalle las posibilidades que ofrecen los lenguajes de programación modernos. Espero que mis artículos ayuden al lector a mirar bajo el capó de estos idiomas y aprender algo nuevo por sí mismos.

Un estudio en profundidad de los conceptos de administración de memoria le permite escribir software más eficiente, porque el estilo y la práctica de la codificación tienen una gran influencia en los principios de asignación de memoria para las necesidades del programa.

Parte 1: Introducción a la gestión de memoria


La administración de memoria es un conjunto completo de mecanismos que le permiten controlar el acceso del programa a la RAM de la computadora. Este tema es muy importante en el desarrollo de software y, al mismo tiempo, causa dificultades o incluso sigue siendo una caja negra para muchos programadores.

¿Para qué se usa la RAM?


Cuando un programa se ejecuta en el sistema operativo de una computadora, necesita acceso a la memoria de acceso aleatorio (RAM) para:

  • cargue su propio bytecode para ejecutar;
  • almacenar los valores de variables y estructuras de datos que se utilizan en el proceso;
  • cargar módulos externos que el programa necesita para completar tareas.

Además del espacio utilizado para cargar su propio código de bytes, el programa utiliza dos áreas de RAM cuando trabaja: la pila (pila) y el montón (montón).

Apilar


La pila se utiliza para la asignación estática de memoria. Está organizado según el principio de "último en llegar, primero en salir" ( LIFO ). Puede imaginar la pila como una pila de libros: solo se permite interactuar con el libro superior: léalo o coloque uno nuevo.

  • Gracias al principio mencionado, la pila le permite realizar rápidamente operaciones con datos: todas las manipulaciones se realizan con el "libro superior de la pila". El libro se agrega a la parte superior si necesita guardar datos, o se toma de la parte superior si es necesario leer los datos;
  • , , , — ;
  • — . , , — , . , , , . , , , — , ;
  • ;
  • ; ;
  • ;
  • (stack overflow), . , ;
  • , ;



JavaScript. , .


El montón se utiliza para asignar memoria de forma dinámica, sin embargo, a diferencia de la pila, los datos en el montón primero se deben encontrar utilizando la "tabla de contenido". Uno puede imaginar que un montón es una biblioteca multinivel tan grande, en la que, siguiendo ciertas instrucciones, puede encontrar el libro necesario.

  • las operaciones en el montón se realizan un poco más lento que en la pila, ya que requieren un paso adicional para buscar datos;
  • Heap almacena datos de tamaños dinámicos, por ejemplo, una lista en la que puede agregar un número arbitrario de elementos;
  • montón común a todos los hilos de aplicación;
  • Debido a su naturaleza dinámica, el almacenamiento dinámico no es trivial y, con él, surge la mayoría de todos los problemas y errores asociados con la memoria. Los lenguajes de programación proporcionan formas de resolver estos problemas;
  • , — ( , ), , , ;
  • (out of memory), , ;
  • , , , .


?


A diferencia de los discos duros, la RAM es muy limitada (aunque los discos duros, por supuesto, tampoco son ilimitados). Si un programa consume memoria sin liberarlo, entonces, en última instancia, absorberá todas las reservas disponibles e intentará ir más allá de los límites de memoria. Entonces simplemente caerá por sí solo o, aún más dramático, derribará el sistema operativo. Por lo tanto, es muy indeseable ser frívolo con las manipulaciones de memoria en el desarrollo de software.

Diferentes aproximaciones


Los lenguajes de programación modernos intentan simplificar el trabajo con memoria tanto como sea posible y eliminar parte del dolor de cabeza de los desarrolladores. Aunque algunos lenguajes venerables aún requieren control manual, la mayoría aún ofrece enfoques automáticos más elegantes. A veces, un lenguaje usa varios enfoques para administrar la memoria a la vez, y a veces un desarrollador puede incluso elegir qué opción será más efectiva específicamente para sus tareas (un buen ejemplo es C ++ ). Pasemos a una breve descripción de los diversos enfoques.

Gestión manual de memoria


El lenguaje no proporciona mecanismos para la gestión automática de la memoria. La asignación y liberación de memoria para los objetos creados permanece enteramente en la conciencia del desarrollador. Un ejemplo de tal lenguaje - el C . Proporciona una serie de métodos ( malloc , realloc , calloc y free ) para administrar la memoria: el desarrollador debe usarlos para asignar y liberar memoria en su programa. Este enfoque requiere gran precisión y cuidado. También es particularmente difícil para los principiantes.

Recolector de basura


La recolección de basura es el proceso de administración automática de memoria en el montón, que consiste en encontrar porciones de memoria no utilizadas que anteriormente estaban ocupadas para las necesidades del programa. Esta es una de las opciones más populares para la administración de memoria en lenguajes de programación modernos. La rutina de recolección de basura generalmente comienza a intervalos de tiempo predeterminados y sucede que su lanzamiento coincide con procesos que consumen recursos, lo que resulta en un retraso en la aplicación. JVM ( Java / Scala / Groovy / Kotlin ), JavaScript , Python , C # , Golang , OCaml yRuby son ejemplos de lenguajes populares que usan el recolector de basura.

  • Marcar y barrer el recolector de basura : este es un algoritmo que funciona en dos fases: primero, marca los objetos en la memoria a los que se hace referencia y luego libera la memoria de los objetos que no han sido etiquetados. Este enfoque se utiliza, por ejemplo, en JVM, C #, Ruby, JavaScript y Golang. Hay varios algoritmos diferentes de recolección de basura para elegir en la JVM, y los motores JavaScript como V8 usan un algoritmo de etiquetado además del conteo de enlaces. Tal recolector de basura se puede conectar en C y C ++ como una biblioteca externa.

    Visualización del algoritmo de etiquetado: los objetos vinculados por enlaces se marcan y luego se eliminan los inalcanzables
  • : — , . . , , , , PHP, Perl Python. C++;


(RAII)


RAII es un lenguaje de software en OOP, cuyo significado es que el área de memoria asignada para un objeto está estrictamente vinculada a su vida útil. La memoria se asigna en el constructor y se libera en el destructor. Este enfoque se implementó por primera vez en C ++ y también se usa en Ada y Rust .

Conteo automático de enlaces (ARC)


Este enfoque es muy similar a la recolección de basura con conteo de referencias, sin embargo, en lugar de comenzar el proceso de conteo en ciertos intervalos de tiempo, las instrucciones para asignar y liberar memoria se insertan directamente en el código de bytes en la etapa de compilación. Cuando el contador de referencia llega a cero, la memoria se libera como parte del flujo normal del programa.

El conteo automático de referencias todavía no permite el procesamiento de enlaces circulares y requiere que el desarrollador use palabras clave especiales para el procesamiento adicional de tales situaciones. ARC es una de las características del traductor de Clang , por lo tanto, está presente en los lenguajes Objective-C y Swift . Además, el conteo automático de referencia está disponible para su uso en Rust.y los nuevos estándares C ++ con punteros inteligentes .

Posesión


Esta es una combinación de RAII con el concepto de propiedad, cuando cada valor en la memoria debe tener solo una variable de propietario. Cuando el propietario abandona el área de ejecución, la memoria se libera inmediatamente. Podemos decir que esto es aproximadamente como contar enlaces en la etapa de compilación. Este enfoque se utiliza en Rust y, al mismo tiempo, no pude encontrar ningún otro lenguaje que utilizara un mecanismo similar.




Este artículo examinó los conceptos básicos en el campo de la gestión de la memoria. Cada lenguaje de programación utiliza sus propias implementaciones de estos enfoques y algoritmos optimizados para diversas tareas. En las siguientes partes, analizaremos más de cerca las soluciones de administración de memoria en los idiomas populares.

Lea también las otras partes de la serie:



Referencias





Si te gustó el artículo, pon un plus o escribe un comentario.

Puede suscribirse al autor del artículo en Twitter y en LinkedIn .

Ilustraciones:
visualización de la pila realizada con pythontutor .
Ilustración del concepto de propiedad: Link Clark, el equipo de Rust bajo Creative Commons Attribution Share-Alike License v3.0 .

Para la prueba de traducción, un agradecimiento especial a Alexander Maksimovsky y Katerina Shibakova

All Articles