El modelo de memoria de Java describe cómo interactúan los subprocesos del lenguaje de programación Java a través de la memoria. Junto con la descripción de la ejecución de código en un solo subproceso, el modelo de memoria proporciona la semántica del lenguaje de programación Java.
El modelo de memoria Java original, desarrollado en 1995, fue ampliamente considerado defectuoso, [1] impidiendo muchas optimizaciones en tiempo de ejecución y no brindando garantías lo suficientemente sólidas para la seguridad del código. Fue actualizado a través del Proceso de la Comunidad Java , como Solicitud de Especificación Java 133 (JSR-133), que entró en vigencia en 2004, para Tiger (Java 5.0) . [2] [3]
El lenguaje de programación Java y su plataforma proporcionan capacidades de subprocesos . La sincronización entre subprocesos es notoriamente difícil para los desarrolladores; esta dificultad se agrava porque las aplicaciones Java pueden ejecutarse en una amplia gama de procesadores y sistemas operativos . Para poder sacar conclusiones sobre el comportamiento de un programa, los diseñadores de Java decidieron que tenían que definir claramente los posibles comportamientos de todos los programas Java.
En las plataformas modernas, el código no se ejecuta con frecuencia en el orden en que fue escrito. El compilador, el procesador y el subsistema de memoria lo reordenan para lograr el máximo rendimiento. En las arquitecturas multiprocesador , los procesadores individuales pueden tener sus propias memorias caché locales que no están sincronizadas con la memoria principal. Por lo general, no es deseable exigir que los subprocesos permanezcan perfectamente sincronizados entre sí porque esto sería demasiado costoso desde el punto de vista del rendimiento. Esto significa que, en un momento dado, diferentes subprocesos pueden ver valores diferentes para los mismos datos compartidos.
En un entorno de un solo subproceso, es fácil razonar sobre la ejecución del código. El enfoque típico requiere que el sistema implemente una semántica de tipo serial para subprocesos individuales de forma aislada. Cuando se ejecuta un subproceso individual, parecerá que todas las acciones que realiza ese subproceso ocurren en el orden en que aparecen en el programa, incluso si las acciones en sí ocurren fuera de orden.
Si un subproceso ejecuta sus instrucciones fuera de orden, entonces otro subproceso podría ver el hecho de que esas instrucciones se ejecutaron fuera de orden, incluso si eso no afectó la semántica del primer subproceso. Por ejemplo, considere dos subprocesos con las siguientes instrucciones, ejecutándose simultáneamente, donde las variables x e y están inicializadas a 0:
Si no se realizan reordenamientos y la lectura de y en el subproceso 2 devuelve el valor 2, la lectura posterior de x debe devolver el valor 1, porque la escritura en x se realizó antes de la escritura en y. Sin embargo, si se reordenan las dos escrituras, la lectura de y puede devolver el valor 2 y la lectura de x puede devolver el valor 0.
El modelo de memoria Java (JMM) define el comportamiento permitido de los programas multiproceso y, por lo tanto, describe cuándo son posibles dichas reordenaciones. Impone restricciones de tiempo de ejecución a la relación entre los subprocesos y la memoria principal para lograr aplicaciones Java consistentes y confiables. Al hacer esto, hace posible razonar sobre la ejecución del código en un entorno multiproceso, incluso frente a las optimizaciones realizadas por el compilador dinámico, los procesadores y las memorias caché.
Para la ejecución de un único subproceso, las reglas son simples. La especificación del lenguaje Java requiere que una máquina virtual Java respete la semántica de ejecución serial dentro del subproceso . El entorno de ejecución (que, en este caso, suele referirse al compilador dinámico, al procesador y al subsistema de memoria) es libre de introducir cualquier optimización de ejecución útil siempre que se garantice que el resultado del subproceso en forma aislada sea exactamente el mismo que habría sido si todas las instrucciones se hubieran ejecutado en el orden en que ocurrieron en el programa (también llamado orden del programa). [4]
La principal salvedad de esto es que la semántica as-if-serial no impide que diferentes hilos tengan diferentes vistas de los datos. El modelo de memoria proporciona una guía clara sobre qué valores se pueden devolver cuando se leen los datos. Las reglas básicas implican que las acciones individuales se pueden reordenar, siempre que no se viole la semántica as-if-serial del hilo, y las acciones que implican comunicación entre hilos, como la adquisición o liberación de un bloqueo , garantizan que las acciones que ocurren antes de ellas sean vistas por otros hilos que ven sus efectos. Por ejemplo, todo lo que sucede antes de la liberación de un bloqueo se verá ordenado antes y será visible para todo lo que suceda después de una adquisición posterior de ese mismo bloqueo. [5]
Matemáticamente, existe un orden parcial llamado orden de ocurrencia anterior sobre todas las acciones realizadas por el programa. El orden de ocurrencia anterior subsume el orden del programa; si una acción ocurre antes de otra en el orden del programa, ocurrirá antes de la otra en el orden de ocurrencia anterior . Además, las liberaciones y adquisiciones posteriores de bloqueos forman aristas en el grafo de ocurrencia anterior. Se permite que una lectura devuelva el valor de una escritura si esa escritura es la última escritura en esa variable antes de la lectura a lo largo de alguna ruta en el orden de ocurrencia anterior , o si la escritura no está ordenada con respecto a esa lectura en el orden de ocurrencia anterior .
El modelo de memoria de Java fue el primer intento de proporcionar un modelo de memoria integral para un lenguaje de programación popular. [6] Se justificó por la creciente prevalencia de sistemas concurrentes y paralelos, y la necesidad de proporcionar herramientas y tecnologías con una semántica clara para dichos sistemas. Desde entonces, la necesidad de un modelo de memoria ha sido más ampliamente aceptada, y se han proporcionado semánticas similares para lenguajes como C++ . [7]
El modelo de memoria de Java describe qué comportamientos son legales en código multiproceso y cómo los subprocesos pueden interactuar a través de la memoria. Describe la relación entre las variables de un programa y los detalles de bajo nivel de almacenamiento y recuperación de las mismas en la memoria o los registros de un sistema informático real. Lo hace de una manera que se puede implementar correctamente utilizando una amplia variedad de hardware y una amplia variedad de optimizaciones del compilador.