Sei sulla pagina 1di 12

LA MEMORIA EN JAVA

Seguro que muchos de vosotros os acordis (y otros muchos segus utilizando)


lenguajes como C, donde la responsabilidad de la gestin de la memoria de los
programas recaa en el programador. En C, se deben utilizar las instrucciones malloc
y calloc para asignar memoria dinmicamente y la funcin dispose para liberar la
memoria que hemos asignado en nuestro programa para que pueda ser reutilizada.
Una de las caractersticas que incorpor el lenguaje Java es la gestin dinmica de
la memoria. Con la programacin en Java nos olvidamos de gestionar la memoria y
toda la responsabilidad recae en el lenguaje. Se asignarn variables segn se vayan
creando en la memoria HEAP y se liberar automticamente esta memoria cuando
no se necesita ms por un proceso de la mquina virtual de Java (JVM) llamado
Garballe
Collector
(GC).

Tipos de Memoria
En Java podemos hablar de tres zonas de memoria:

La ZONA DE DATOS, donde se almacenan las instrucciones del programa,


las clases con sus mtodos y constantes. Esta zona es inmutable durante
todo el periodo de ejecucin del programa.

La memoria HEAP, donde se almacenan las variables que se crean en los


programas con la sentencia "new". Esta zona de memoria es dinmica e ir
creciendo conforme se vayan creando objetos desde nuestro programa.

La memoria STACK, donde se almacenan los mtodos y las variables


locales que se crean dentro de los mismos. Esta zona de memoria es
esttica y no se modifica durante el desarrollo del programa. Cada vez que
un mtodo se llama se coloca al comienzo de la pila y se almacenar
informacin como el nmero de la lnea que se est ejecutando as como
todos los valores de las variables locales. Los Threads o Hilos tienen su
propia pila de llamadas. Un error que nos puede ocurrir es que al ejecutar
nuestro
programa
obtengamos
la
excepcin
java.lang.OutOfMemoryException por un StackOverflow. La causa podra ser
por ejemplo que estamos realizando una llamada recursiva y no hayamos
implementado correctamente la condicin de parada.

Diferencias entre memoria STACK y HEAP (Imagen:

prietopa)
Ventajas y Desventajas de la Gestin Dinmica
Una desventaja de la gestin de memoria en Java es que nosotros no controlamos
donde queremos asignar nuestra memoria. Asignar memoria en el HEAP es ms
costoso y tambin liberar dicha memoria, por eso, a veces es deseable almacenar
un objeto en la memoria STACK.
Entre las ventajas de la gestin dinmica es que no tenemos que preocuparnos por
asignar y desasignar memoria, puesto que es un proceso dinmico. Adems la
JVM internamente utiliza una optimizacin llamada anlisis de escape (en
ingls scape analysis) que realiza comprobaciones para saber si un objeto se utiliza
slo en un thread o un mtodo y crearlo en la memoria STACK, incrementando el
rendimiento de nuestros programas.
Garbage Collector
Como hemos mencionado anteriormente, el Garbage Collector o recolector de
basura es un proceso automtico de baja prioridad que se ejecuta dentro de la JVM.
Se encarga de limpiar aquella memoria del HEAP que ya no se utiliza y por tanto,
podra ser utilizada por otros programas. Cmo sabe la memoria que no se utiliza?
Un objeto podr ser limpiado cuando desde el STACK ninguna variable haga
referencia al mismo.
Si queremos saber cuando se ejecutar el GC deberemos utilizar el
parmetro "-verbose:gc" antes de arrancar nuestra JVM.

Garbage Collector (Imgen: oracle)

El proceso GC tambin se puede ejecutar manualmente desde un programa Java si


creemos que en un punto determinado hemos podido dejar muchos objetos sin

referenciar. A continuacin os muestro mediante un pequeo programa Java cmo


se puede calcular la memoria libre en un momento determinado en la JVM y cmo
ejecutar el Garbage Collector manualmente desde un programa:

1
2
3
4
5
6
7
8
9
1
0
1
1
1
2
1
3
1
4
1
5
1
6
1
7
1
8
1
9
2
0
2
1
2
2
2
3
2
4
2
5
2
6
2
7
2
8

package es.jpascu.test;
import java.util.ArrayList;
import java.util.List;
public class TestJVMMemory {
private static final long MEGABYTE = 1024L * 1024L;
public static long bytesToMegabytes(long bytes) {
return bytes / MEGABYTE;
}
public static void main(String[] args) {
List<item> list = new ArrayList<item>();
for (int i = 0; i <= 1000000; i++) {
Item item = new Item("1", "UN MURCIANO EN EL POLO",
2);
list.add(item);
}
// Obtenemos la runtime de Java
Runtime runtime = Runtime.getRuntime();
// Ejecutamos el proceso de Garbage Collector
runtime.gc();
// Calculamos la memoria mxima y la memoria
disponible
long memory = runtime.totalMemory() runtime.freeMemory();
long memoriaMax = runtime.maxMemory();
System.out.println("Memoria mxima en bytes: " +
memoriaMax);
System.out.println("Memoria mxima en MB: "
+ bytesToMegabytes(memoriaMax));
System.out.println("Memoria utilizada en bytes: " +
memory);
System.out.println("Memoria utilizada en MB: "
+ bytesToMegabytes(memory));
}
}
</item></item>

2
9
3
0
3
1
3
2
3
3
3
4
3
5
3
6
3
7
3
8

Configuracin de tamao de memoria en la JVM


La JVM asigna el tamao inicial y el tamao mximo de la memoria HEAP cuando
arranca pero nosotros podemos modificar esos parmetros si lo creemos oportuno.
Por ejemplo, a veces nos podra dar una excepcin
java.lang.OutOfMemoryException en nuestro servidor J2EE (Tomcat,
Weblogic...) porque una aplicacin est consumiendo demasiada memoria y el
tamao que puede alcanzar la memoria HEAP es demasiado pequeo. Tambin
podremos modificar el tamao mximo de la memoria HEAP que nunca deber
exceder el tamao de la memoria fsica de la mquina.

Para indicar el tamao mnimo que tiene la memoria HEAP utilizamos el parmetro:

-Xms1024m. Establecemos el tamao mnimo del HEAP en 1024 MB.

Para indicar el tamao mximo que tiene la memoria HEAP utilizamos el


parmetro:

-Xmx1800m. Establecemos el tamao mximo del HEAP en 1800MB.

Modificar tamao de memoria HEAP en ECLIPSE

Write once, run anywhere


Despus de haber visto cmo se gestiona la memoria vamos a repasar un punto
que a mi entender es muy interesante en la plataforma Java, la compilacin y
ejecucin de los programas.
El ttulo del apartado es un eslogan famoso para promocionar el lenguaje Java y
hace referencia al soporte multiplataforma de Java. Por qu? Porque en Java
escribimos un programa una vez y lo compilamos a bytecodes. Este cdigo
compilado nos sirve para ejecutar el programa en diversas plataformas (mviles,
Linux, Windows...). Para ello slo necesitamos diferentes mquinas virtuales (JVMs)
que traduzcan los bytecodes al cdigo mquina de la plataforma correspondiente.

Compilador JIT (Imagen: profejavaoramas)


En las primeras versiones de Java la JVM era simplemente un interprete de
bytecodes. Esto haca que los programas se ejecutaran con lentitud, ya que se
tena que interpretar y ejecutar los bytecodes a la vez sin realizar ninguna
optimizacin.
En las versiones actuales de la JVM se ejecutan los programas combinando
la interpretacin de bytecodes y la compilacion JIT o Just-In-Time. Qu
demonios es esto? Pues que la JVM analiza bytecodes a medida que se van
interpretando e identifica los bytecodes que se ejecutan con ms frecuencia (puntos
activos o 'hot spots'). Estos bytecodes son traducidos a lenguaje mquina

correspondiente por el compilador JIT. Cuando la JVM encuentre de nuevo estos


puntos activos nuevamente, ejecutar directamente el cdigo mquina
disminuyendo el tiempo de ejecucin del programa y aumentando el rendimiento.

Compilador JIT.
En las primeras versiones de Java, la JVM era tan slo un intrprete de cdigos de
byte de Java. Esto haca que la mayora de los programas se ejecutaran con
lentitud, ya que la JVM tena que interpretar y ejecutar un cdigo de bytes a la vez.
Por lo general, las JVMs actuales ejecutan cdigos de bytes usando una combinacin
de la interpretacin y la denominada compilacin justo a tiempo (JIT). En este
proceso, la JVM analiza los cdigos de bytes a medida que se interpretan, buscando
puntos activos: partes de los cdigos de bytes que se ejecutan con frecuencia.

Para estas partes, un compilador justo a tiempo (JIT y conocido como compilador
HotSpot de Java) traduce los cdigos de bytes al lenguaje mquina correspondiente
a la computadora. Cuando la JVM encuentra estas partes compiladas nuevamente,
se ejecuta el cdigo en lenguaje mquina, que es ms rpido. En consecuencia, los
programas en Java en realidad pasan por dos fases de compilacin: una en la cual el
cdigo fuente se traduce a cdigo de bytes (para tener portabilidad a travs de las
JVMs en distintas plataformas computacionales) y otra en la que, durante la
ejecucin, los cdigos de bytes se traducen en lenguaje mquina para la
computadora actual en la que se ejecuta el programa.

Diferencias entre STACK y HEAP de la JVM

Almacena

STACK

HEAP

Variables
locales

Objetos (clases,
mtodos,
instancias)

Nunca se

Se puede

puede
manipular
directamente

redimensionar y
tiene el Garbage
Collector

Tiene acceso
al procesador,
stack pointer

no

La memoria no
necesita ser
Idem
contigua
Cada hilo tiene Comn a toda la
un stack
JVM
OutOfMemory

No tiene
Necesita ms
espacio para
tamao.
un nuevo hilo.

StackOverflow

Requiere ms
espacio del
permitido

Ejemplo:
Heap (04567= 83)
name = Susan
age = 26
city= London
height= 57
sex= female

var blue
var red
ref 0456783= (Heap reference)
var tom
ref 0498702= (Heap reference) Heap (04987= 02)
name= Paul
var diane
age= 21
city = Glasgow
height = 60
sex = male

Ejemplo grafico

Bibliografia:
http://wiki.answers.com/Q/What_is_difference_between_heap_memory_and_stack_m
emory
- http://www.sap-img.com/java/difference-between-stack-and-heap.htm
- http://www.soygeek.com/index.php/2008/02/12/almacenamiento-en-java/
- http://net.pku.edu.cn/~course/cs101/2008/resource/heapAndStack.html

Cul es la diferencia entre la memoria de almacenamiento dinmico y


memoria de pila?
Respuesta:
Diferencia entre la pila de memoria Heap vs
"La memoria de pila" y "memoria Heap" son fsicamente iguales.
El mismo chip de RAM puede ser utilizado como memoria de pila
cuando se ejecuta un programa y despus utiliza como memoria de
pila cuando se ejecuta algn otro programa.
La diferencia est en la forma en que se utilizan.

2 Visin
La JVM (mquina virtual Java).
La maquina virtual de java -o JVM en sus siglas en ingls- es el anclaje fsico de nuestro
maravilloso lenguaje multiplataforma con el Sistema Operativo correspondiente y, como tal,
ocupa y utiliza memoria del sistema. Bien, esto es evidente pero, lo que es menos conocido,
es como est implementada esta gestin de memoria.
La JVM se arranca reservando, por defecto, 64MB de memoria para trabajar que va
ampliando segn necesidades. Sin embargo, estas sucesivas ampliaciones automticas
llegan a un lmite. Podemos gestionar nosotros mismos estos lmites -tanto por arriba como
por abajo- gracias a unos parmetros con los que se puede acompaar al comando de
arranque de la JVM.
Esto es IMPORTANTE si sabemos que nuestra aplicacin va a requerir una gran cantidad de
memoria para su funcionamiento y queremos evitar un OutOfMemoryError o si, por el
contrario, nuestra aplicacin compite con otras de mayor prioridad en un sistema y queremos
que utilice solo una parcela de memoria.
PARMETROS DE GESTIN DE MEMORIA
Antes de nada, es importante tener en cuenta que los parmetros de gestin de memoria
son, en la mayora de los casos, especficos de la maquina virtual. As, el parmetro -Xlp es
exclusivo de la maquina virtual implementada por IBM y se puede utilizar exclusivamente con
esta.
Aunque hay un montn de implementaciones de JVMs, en la prctica, solo 3 o 4 son utilizadas
en un porcentaje significativo y, en cualquier caso, siempre tendremos que probar nuestra
aplicacin con la implementacin de referencia, la de SUN, para evitar posibles problemas de

compatibilidades. Por eso, nos centraremos en los parmetros que se puedan utilizar con las
maquinas virtuales de SUN.
Los parmetros de gestin de memoria se identifican por un sistema de prefijos que indican
la naturaleza de dichos parmetros, as y segn SUN:
* Los parmetros que comienzan con -X no son estndar, no se garantiza su implementacin
en todas las implementaciones de la JVM y pueden cambiar en futuras versiones del JDK.
* Los parmetros que comienzan con -XX no se consideran estables (??) y no se
recomienda su uso para usuarios sin un conocimiento especifico.
Los principales parmetros de gestin de memoria son:
* Xms < tamao > establece la cantidad inicial de memoria
* Xmx < tamao > establece la cantidad mxima de memoria
* Xss < tamao > establece el tamao mximo de pila de cada hilo JAVA
Es decir, si quisiramos arrancar nuestra aplicacin con una cantidad de memoria asignada
de 64MB y queremos que llegue a utilizar un GB, por ejemplo, tendramos que lanzar un
script de la siguiente manera:
java -Xms64m -Xmx1024m nombre de nuestra aplicacin
Es importante conocer en profundidad que implican y para que se utilizan cada uno de estos
parmetros:
* Los parmetros Xms y Xmx establecen el tamao inicial y mximo de la cantidad de
memoria que la JVM utilizar para ejecutar programas java pero, esto no implica que nuestro
proceso java ocupe en memoria un tamao mximo como el establecido en el parmetro
Xmx. Como se ha especificado antes, la JVM es un proceso en si mismo y, como tal, necesita
memoria para poder cargar su cdigo y datos. Por tanto, un proceso java puede llegar a
ocupar mas espacio en memoria que el determinado en Xmx.
* El parmetro Xss sirve para determinar cual es la cantidad de memoria mxima que podr
utilizar un hilo ejecutado dentro de la maquina virtual. Para aquellos que no estn
familiarizados con la gestin de hilos, pondremos un ejemplo de sencilla comprensin:
imaginemos una aplicacin web desarrollada en java. Esta aplicacin web tendr sus datos
comunes y ocupar X espacio en memoria. Cada vez que alguien haga una peticin a nuestra
pgina web, se crear un hilo de ejecucin independiente que tendr sus propios datos (por
ejemplo, los datos de un formulario de registro en nuestra pgina) y que no sern
compartidos por el resto de hilos que se estn ejecutando en dicho momento (nuestra pgina
web no es monousuario puede tener un montn de usuarios concurrentes). Bueno, en este
caso, toda la memoria que utilice cada uno de los hilos se suma para el cmputo de memoria
global determinado por el parmetro Xmx pero, adems cada hilo comprueba que no supere
el mximo de memoria que determina el parmetro Xss. Si se supera, se lanza un
StackOutOfMemoryError.
Un caso comn y conocido donde se puede dar un StackOutOfMemoryError es a la hora de
usar reflexin en procesos XSLT por lo que, probablemente tengamos que subir el tamao
mximo de pila por hilo si utilizamos este tipo de tecnologa.
PARMETROS AVANZADOS DE GESTIN DE MEMORIA
Hasta ahora, hemos visto los parmetros ms bsicos de gestin de memoria pero hay
muchsimos ms. Vamos a centrarnos en dos de los ms importantes o, lo que es lo mismo,
de los que pueden solucionarnos problemas tanto de rendimiento como de funcionamiento
(seamos sinceros, nadie se interesa por esto hasta que el marrn le estalla entre las manos).
El problema es que, antes de poder especificar los comandos, tendremos que tragarnos algo
de literatura para comprender de que estamos hablando.
EL GARBAGE COLLECTOR

Toda mquina virtual viene con un recolector de basura o Garbage Collector. Este maravilloso
invento es el que consigue que los programadores java nos olvidemos por completo de tener
que lidiar con la gestin de la memoria (y es el culpable, por otro lado, que todo este tema
nos suene tan raro y distante y de ah este articulo).
El Garbage Collector libera el espacio de memoria ocupado por objetos que no van a volver a
ser utilizados. El proceso de localizacin y eliminado de esos objetos puede ser tan pesado
que llegue a parar una aplicacin hasta su conclusin, por lo que es especialmente
importante que lo configuremos correctamente.
La JVM divide el espacio en generaciones o particiones en los que poder aplicar sofisticados
algoritmos de recoleccin. Cuando alguno de estos espacios se llena, se fuerza una
recoleccin de objetos y, la eficiencia de este sistema se basa en que la mayora de los
objetos tienen una vida til muuuy corta. El espacio en memoria, se divide generalmente en
dos particiones: la nueva y la vieja generacin.
Cuando el tamao de la nueva generacin se llena, se invoca una recoleccin de objetos muy
rpida (GC) que acaba con los objetos que no vayan a ser utilizados y que traspasa a la vieja
generacin los objetos con vida. Cuando la vieja generacin se llena, se invoca una
recoleccin completa (Full GC) que implica a toda la memoria y que es mucho ms lenta que
la recoleccin rpida. Para entender un poco mas grficamente de lo que estamos hablando,
veamos unas trazas de una JVM:
[GC 50650K->21808K(76868K), 0.0478645 secs]
[GC 51197K->22305K(76868K), 0.0478645 secs]
[GC 52293K->23867K(76868K), 0.0478645 secs]
[Full GC 52970K->1690K(76868K), 0.54789968 secs]
En estas trazas se puede ver cmo, tras la primera recoleccin rpida (GC), se pas de
50650Kb usados a 21808Kb (de un total de 76868Kb que es el espacio asignado mediante
-Xmx). Tambin se puede ver cmo, tras la recoleccin completa (Full GC) se pas a un uso
de tan solo 1690Kb pero, a cambio de casi medio segundo de inactividad de nuestra
aplicacin, que es lo que tardo la recoleccin completa en llevarse a cabo.
Por tanto, no parece tener mucho sentido que tengamos un espacio de 4GB asignado a
nuestro proceso java y un espacio de 2MB -que es lo que viene asignado por defecto- para
nuestra nueva generacin. Normalmente, se recomienda que pongamos el tamao de la
nueva generacin que queramos mientras no supere el tamo determinado en -Xmx y, como
buena prctica, se recomienda asignar la mitad del espacio total de memoria (Ej. java
-Xmx1024m -Xmn512m nombre de nuestra aplicacin). As:
* Xmn < tamao > establece la memoria asignada para la nueva generacin.
Tambin se puede forzar la recoleccin mediante una llamada al sistema en java con
System.gc() pero, no se recomiendo su uso porque fuerza recolecciones completas e impide
la escalabilidad de los grandes sistemas.
LA MEMORIA PERMANENTE
Un concepto que, a veces, no queda muy claro es que todo esto de lo que estamos hablando
se refiere a memoria voltil, es decir memoria ocupada por datos con una esperanza de vida
mas o menos larga pero, en ningn caso, pensados para ser utilizados durante todo el tiempo
de ejecucin de nuestro proceso. Sin embargo, si hay datos permanentes que utilizar
nuestro proceso java.
Estos datos (classes, libreras, etc.) se almacenan en la memoria permanente que funciona
adicionalmente al espacio establecido por nosotros para la memoria voltil mediante el
parmetro -Xmx.
Que para que se utiliza esto realmente? Para la carga y descarga de classes dinamicamente
y, cuando alguien se pregunte que clases son esas que se cargan dinamicamente debe
recordar que nuestras JSPs se convierten en classes dinamicamente segn se van invocando

(aunque se pueda efectuar una precompilacin para ganar en rendimiento) y que las libreras
invocadas por nuestras aplicaciones tambin utilizan este espacio.
Cmo puede afectarnos esto? Bueno, la JVM establece por defecto un tamao mximo de
64mb para la memoria permanente. Si nuestra aplicacin empieza a generar clases como si
no hubiera maana (nadie duda de que todos nosotros hace aplicaciones con TRILLONES de
usuarios concurrentes) y llenamos este espacio en memoria, forzaremos una recoleccin
completa del Garbage Collector que penalizar el rendimiento de nuestra aplicacin. En un
escenario ms real, si nos hemos dedicado a agregar y usar libreras y libreras con sus
dependencias y nuestra memoria permanente no puede cargar todas las que necesitamos,
daremos con un OutOfMemoryError que nos romper el Chi a nosotros y a nuestros usuarios
(no te digo ya a nuestro jefe). No parece que tenga muchos sentido, por otro lado, que le
demos a nuestra mega-aplicacin 2GB de memoria voltil y la dejemos con 64mb para cargar
clases y libreras verdad?. As:
* XX:PermSize=< tamao > establece el tamao mnimo de la memoria permanente
* XX:MaxPermSize=< tamao > establece el tamao mximo de la memoria permanente
Si, por ejemplo, queremos utilizar 128mb para nuestra memoria permanente, ejecutaremos
algo como java -Xmx1024m -Xmn512m -XX:PermSize=128m nombre de nuestra
aplicacin)
Es importante recalcar que la memoria permanente es adicional a la memoria voltil
establecida por el comando -Xmx. Por eso, si arrancamos un proceso java con un permSize de
256mb y un -Xms de 256mb, el espacio total de memoria usada ser de mas de 512mb (256
de memoria voltil + 256 de memoria permanente + la memoria que utilice la maquina
virtual en si) Ej.

$ java -Xms256m -Xmx256m -XX:PermSize=256m -XX:MaxPermSize=256m Hello


$ pmap 6472
6472: /usr/java1.3.1/bin/../bin/sparc/native_threads/java -Xms256m
-Xmx256m
total 550544K

OPTIMIZACIN DEL RENDIMIENTO EN ARRANQUE Y EN TIEMPO DE EJECUCIN


Otra optimizacin que hay que tener en cuenta es la proporcionada por los diferentes perfiles
de la maquina virtual y el uso del compilador de cada uno de los mismos. As, el perfil por
defecto es el de -client que prioriza la velocidad de arranque del proceso antes que el
rendimiento general de la aplicacin. Por el contrario, el perfil -server prima el rendimiento en
tiempo de ejecucin en vez que el arranque rpido del proceso (adems de modificar ciertos
valores por defecto como, por ejemplo, el tamao por defecto de la memoria permanente
que pasa de 32 a 64mb).
Para utilizar el perfil -server solo hay que introducirlo como parmetro en la ejecucin de
nuestro proceso java.
CONCLUSIONES
Por ltimo, adems de la correcta configuracin de nuestra maquina virtual, otra de las cosas
que debemos tener en cuenta es seguir una serie de buenas practicas a la hora de
programar nuestras aplicaciones aunque dejaremos esto para otro post. Se pueden encontrar
mltiples sitios con listas de optimizacin de memoria para nuestras aplicaciones pero
destaquemos un articulo reciente sobre antipatrones de memoria que hay que evitar y que
puede ensearnos mucho.

Tambin es importante tener en cuenta el enorme muestrario de aplicaciones de profiling y


monitorizacin disponibles en el mercado, gratuitas y de pago, para comprender que pasa
con nuestra memoria, cuando y porqu.
BIBLIOGRAFA
Artculo publicado bajo licencia CC - Some rights reserved en el blog de David Bonilla:
http://sixservix.com/blog/david/2009/08/21/gestion-de-memoria-en-java/

Potrebbero piacerti anche