Cómo funciona la memoria en Java

Como he tenido que empaparme de su funcionamiento, aprovecho y pongo un post sobre conceptos básicos de la memoria en Java.

Nota: Este post es de hace unos años así que la estrategia por defecto de la JVM puede haber cambiado, aunque los conceptos son los mismos.

Heap y Non-Heap

Para empezar, Java divide la memoria en dos segmentos bien diferenciados:

  • Heap: Objetos del usuario, variables, ...
  • Non-Heap/Perm: Código, metadatos, ...

La que más nos interesa es la Heap, porque es la que podemos "controlar". A la memoria Non-Heap (Perm) se le puede configurar el tamaño con el comando MaxPermSize. Pero esto sólo es útil si la aplicación va a cargar o generar dinámicamente muchas clases diferentes.

Generaciones

La memoria heap se divide a su vez en dos generaciones según su tiempo de vida:

  • Young Generation
  • Old Generation

Normalmente la generación joven está compuesta de variables locales y objetos temporales, mientras que la generación vieja suele componerse de estructuras que son necesarias durante toda la ejecución: configuraciones, ventanas gráficas,...A su vez, la generación joven se divide en dos:

  • Eden: Aquí es donde se crean los objetos inicialmente
  • Survivor: Es como el limbo a través del cual se pasa de la generación joven a la vieja. Suele estar compuesto a su vez en dos partes.

Se puede especificar el ratio entre el espacio de la generación joven y el espacio de la generación vieja con el comando de la máquina virtual -XX: NewRatio .

El Recolector de Basura (Garbage Collector)

El Recolector de Basura ( Garbage Collector ) realiza dos tipos de tareas periódicas:

  • Minor Collection: Que revisa de forma rápida la generación joven.
  • Major Collection: Que revisa de forma exhaustiva toda la memoria, fundamentalmente la generación vieja.

El Recolector de Basura se ejecuta a la vez que la ejecución normal del programa, por lo que cada ejecución suya implica una pequeña pausa (de milisegundos normalmente) en todos los threads que se estén ejecutando en ese momento. Mientras la memoria permanezca más o menos liberada, el Recolector de Basura se limitará a las minor collections, para no interferir con el flujo de la aplicación.

Para tener una idea más exacta de cómo trabaja, se puede añadir la opción -verbose:gc a la máquina virtual. Este comando irá imprimiendo cada vez que se ejecute el Recolector de Basura cuanto espacio de memoria ha conseguido liberar.

Estrategias

Existen varias implementaciones del Recolector de Basura, siendo la más común el Serial Collector. Esta implementación, además de ser la más sencilla, utiliza sólo un procesador. Si se está usando una máquina más potente, con varios procesadores y una buena cantidad de memoria física, se puede activar el Parallel Collector , que utiliza varias CPUs a la vez. Esto mejora la forma en la que funciona el Recolector de Basura, pero también puede paralizar a ratos el flujo de ejecución normal de la aplicación.

De todas formas, para el buen funcionamiento y limpieza de la memoria, conviene tener pequeños objetos temporales de vida corta antes que objetos largos y duraderos. Los pequeños objetos temporales se quedarán en el Eden, por lo que se recolectarán mucho antes y de forma mucho más rápida.

Así mismo, tener objetos en memoria sin usar, aunque no molesten a la ejecución del programa, ralentizan la ejecución del Recolector de Basura, porque tendrá que procesarlos una y otra vez en todas sus pasadas.

Forzando la recolección de basura

En algún momento puede parecer tentador forzar una ejecución del Recolector de Basura llamando a System.gc(). Sin embargo, esto lo único que hará será forzar una major collection de forma asíncrona, rompiendo completamente toda la heurística del Recolector de Basura. Es tan desaconsejable que hasta hay una opción en la máquina virtual para desactivar estas llamadas: -XX:+DisableExplicitGC .

Para ayudar a la tarea del Recolector de Basura, existen tres tipos de referencias :

  • Weak: No impide al recolector de basura llevárselo.
  • Soft: Como el weak, pero el recolector de basura lo respeta un poco más y sólo se lo lleva si le hace falta memoria. Útil para cachés, pero puede ser engañoso.
  • Phantom: Siempre devuelve null, no accede al objeto realmente, pero puede servir servir para limpiar antes de que se lleven al objeto que lo enlaza.

Una clase que explica bastante bien el funcionamiento de este tipo referencias es  WeakHashMap: Funciona como un HashMap, pero usando referencias Weak. De esta forma, si las claves que contiene sólo están referenciadas en dicho HashMap, considera que ya no son útiles y las borra.

Referencias útiles:

Un comentario en “How does Memory work on Java”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *