martes, 20 de diciembre de 2011

RWJ. La memoria en la JVM

Aunque muchas veces se nos olvida, la JVM es un proceso del sistema operativo como otro cualquiera. Si, si. Lo que leéis. Y como en el resto de procesos, la memoria está dividida en tres regiones :  el stack, el heap y el code segment 

  • el stack es la zona de memoria donde se almacenan los parámetros que se pasan a una función en una llamada, las variables locales e información de retorno. Cada vez que se llama a una función desde dentro de otra, se apilan los parámetros de la nueva función y sus variables hasta que se haga return.
  • el heap es el área de memoria en el que "se hacen los malloc() " . En él se guardan las estructuras de datos permanentes en memoria. Y en los lenguajes orientados a objetos, se crean los objetos. Y si se usa mucho, acaba por llenarse.
  • el code segment es la zona de memoria donde reside el código del proceso. En el caso de la JVM, es la parte del código que no está programado en Java. Es decir, el core nativo de la JVM.
Como comentaba, la JVM es un proceso como otro cualquiera. Y la gran idea que tuvo Sun fue hacer un proceso que ejecutase procesos. Y esos pseudoprocesos están escritos en bytecode, una especie de código máquina para JVM, independiente de la plataforma sobre la que se ejecute la JVM.

Como tuvieron tiempo para pensárselo, hicieron las cosas a conciencia: los programas Java se compilan a  bytecode común y se ejecutan en la JVM -específica por sistema operativo- . La orientación a objetos  está muy presente en todo el diseño del lenguaje y, como no, en el de la propia JVM. La máquina virtual ofrece mecanismos para liberar la memoria de los objetos obsoletos, llamado garbage collector. Y el propio código de los programas Java forma parte del paradigma objetual. 

El área de memoria de los procesos de la JVM tiene un diseño propio, muy parecido al de los sistemas operativos, pero diseñado para facilitar el garbage collecting: 
  • el stack, con el mismo funcionamiento comentado antes
  • el heap, en el que se crean todos los objetos utilizados, y sobre los que posteriormente se hará recolección de basura, con dos partes :
    • young generation: en el que se crean los nuevos objetos (eden) y con dos survival spaces por los que van pasando los objetos antes de llegar a la
    • tenured (old) generation, en el que acaban los objetos persistentes a los gc.
  • el permanent generation space , equivalente al code segment. En él se va cargando la información de las clases y métodos utilizados. La memoria usada en este espacio no se libera jamás, y hará falta más espacio cuantas más clases utilice el proceso.
A nivel de implementación de sistema operativo, tanto el heap como el permgen space se guardan en el heap del proceso JVM, aunque cuando hablamos de Java, solemos decir que el permgen space no es parte del heap.

Por cuestiones de diseño y seguridad, la memoria máxima que el proceso de la JVM va a utilizar se define en el momento de arrancarla, y no es posible aumentarla. ¿Cómo? Con las siguientes opciones :
  • -Xms64m , que indica que el tamaño del heap reservado al arrancar la JVM será de 64 megas.
  • -Xmx1024m, que limita a 1024 megas el tamaño máximo de heap reservable.
  • -XXMaxPermSize=128m , limita a 128 megas el tamaño máximo de la permanent generation.
La memoria máxima reservada por defecto para la JVM de Sun es -Xmx64m . Pero estos y otros muchos parámetros son configurables .

Leído todo esto, es posible que empecéis a distinguir los diferentes errores de memoria de la JVM
  • java.lang.StackOverflowError . Error de desbordamiento de pila, que ocurre cuando se apilan tantas llamadas a funciones que se salen de su espacio de memoria ( normalmente con llamadas recursivas infinitas ) 
  • java.lang.OutOfMemoryError : heap space . Ocurre cuando el heap se llena por completo y el gc no es capaz de liberar espacio. Puede deberse a memory leaks o a que nuestro programa necesita más memoria.
  • java.lang.OutOfMemoryError : PermGen space failure . Cuando el permgen space se llena a tope. Puede deberse a que no hay memoria suficiente para cargar todas las clases que llamamos (¿¿nos cargamos todo apache commons?? ) o a memory leaks en classloaders que recargan clases en caliente pero no liberan bien la memoria de las anteriores.
Y en cuanto al tema del garbage collector, yo creo que se merece una entrada propia. ¿Se echa de menos algún dibujo en el artículo?











1 comentario:

  1. Se me olvidaba: la plataforma .NET de M$ es MUY MUY parecida en concepción a esta. La única diferencia es que los procesos .NET no tienen limitado el tamaño máximo de memoria. Es decir, que un programador reservando un array muy gordo podría acabar por tumbar el sistema.

    ResponderEliminar