HOW TO: Garbage Collection / JVM

De Thubanpedia
Saltar a: navegación, buscar


HOW TO: Garbage Collection / JVM

Existen diferentes tipos de JVM: aunque la distribuida por Oracle, HotSpot, es la que se usa con mayor frecuencia en Windows, también existen en el mercado variantes que se pueden encontrar en otros SO como las de Ubuntu que presenta una JVM open source, las de Apple, las de IBM (incluidas en Websphere) o la que viene con el servidor BEA Weblogic conocida como JRockit. La mayoría de estas JVM funcionan igual que HotSpot, excepto por JRockit que maneja la memoria de manera diferente ya que no tiene un PermGen separado.

Conformación de una JVM

JVM

Permanent Generation (PermGen)

Es utilizado únicamente por la JVM, no se pueden guardar datos en esta sección. Se utiliza este espacio para mantener la metadata de los objetos que se van creando, por lo tanto, cuanto más objetos creemos, más grande será PermGen.

A su vez, el tamaño del PermGen se controla con dos parámetros:

-XX:PermSize: es el tamaño inicial.

-XX:MaxPermSize: es el tamaño máximo.


Importante: cuando corremos grandes aplicaciones JAVA, frecuentemente se configura estas dos variables con el mismo valor. Esto mejora el rendimiento ya que se ahorra el trabajo de analizar si tiene que modificar su tamaño (es una operación que tiene un costo alto).

Heap

El Heap es la memoria principal donde se almacenan todos los objetos. Se divide en Old Generation y New Generation que a su vez se subdividen internamente.

El tamaño también es controlado por dos parámetros:

-Xms: representa el mínimo.

-Xmx: representa el máximo.


Cuando creamos un nuevo objeto, por ejemplo byte[] data = new byte[1024] , éste se crea directamente en un área llamada Eden. Ahora bien, si se quiere crear un objeto, pero no hay suficiente espacio en el Eden, la JVM deberá realizar un Garbage Collection. Esto significa que buscará todos aquellos objetos en la memoria que no sean necesarios porque ya no tienen referencia.

El Garbage Collector permite abstraernos de manejar la memoria por código como se hace en C o en Objective-C de Iphone ya que toda esta responsabilidad recae en la JVM que busca todos aquellos objetos que ya no sean necesarios o no estén en uso y los elimina.

Para hacer Garbage Collection, la JVM emplea ciertos algoritmos que se pueden controlar cambiando sus parámetros.


Algoritmo Copy Collection

String a = "hello";
String b = "apple";
String c = "banana";
String d = "apricot";
String e = "pear";


Esto generará cinco objetos en la memoria del Eden (ver slots amarillos en la imagen a continuación). Supongamos que liberamos a,b,c y e poniéndolos a null:

a = null;
b = null;
c = null;
e = null;


Asumiendo que no hay otras referencias a esos objetos, serán marcados como “sin uso” (ver slots rojos en la imagen a continuación).

Algoritmo Copy Collection


Si tratamos de crear un nuevo objeto, la JVM encontrará que el Eden está completo y que es necesario un Garbage Collection. El más simple de los algoritmos de Garbage Collection se conoce como Copy Collection y está representado en el tercer diagrama. El algoritmo marca los objetos sin uso en rojo (en el ejemplo, String a, b, c y e) y cuando necesita espacio copia los objetos que están en uso (String d) a una zona llamada Survivor (sobreviviente). Ahora teniendo los objetos que queremos mantener en la zona Survivor, elimina todo lo que queda en “Eden”.

Cuando se utiliza este tipo de algoritmo, cualquier thread que no sea el del Garbage Collector está pausado. Esto es necesario para que los otros threads no traten de modificar la memoria al mismo tiempo que el GC (se perderían los cambios). Este tipo de algoritmo no debería de ser problema en una aplicación JAVA pequeña pero si tenemos un HEAP de 8GB se tomaría muchísimo tiempo en correr este algoritmo (por el Eden es demasiado grande). Esto haría que la aplicación se congele por unos cuantos segundos o minutos. Por eso existen otros algoritmos y son más utilizados que este.

Algoritmo Mark-Sweep-Compact Collection (CMS)

Este algoritmo consta de tres etapas: La primera fase (Mark) se encarga de buscar los objetos que no están en uso (ver objetos en rojo en la Imagen 3). En la segunda fase (Sweep) se borran esos objetos de memoria (ver los slots vacíos). Y en la última fase (Compact), se compactan los objetos para evitar que queden espacios vacios y dejar la mayor cantidad de memoria continua posible en caso de que se tenga que crear un objeto grande.

Algoritmo Mark-Sweep-Compact Collection


Se puede visualizar cómo funciona el algoritmo activando la herramienta jvisualvm que está incluida en el Java Development Kit (JDK) en el directorio BIN:

Algoritmo Mark-Sweep-Compact Collection


En la imagen se puede ver el PermGen, el OldGen y el Eden y, depende el algoritmo, dos espacios Survivor S0 y S1). Las barras en color indican la memoria utilizada. En la parte izquierda hay un log que indica cuánto tiempo invirtió la JVM en realizar un Garbage Collection y cuánta memoria utilizó para hacerlo.

Cuando nuestra aplicación comience a generar objetos y no corre el Garbage Collection, entonces comienzan a moverse objetos al sector Survivor. Si no hay espacio suficiente, hay poca basura (pocos objetos sin uso) y además los objetos utilizados son muy grandes, los envía a la OldGen.

Pero cuando el Eden y el OldGen estén llenos la aplicación crashea y generará un OutOfMemoryException. Esto no tiene nada que ver con la memoria física de la máquina, sino con la destinada a la JVM. Por otro lado, si nos quedamos sin memoria en el PermGen, se generará un OutOfMemoryException: PermGen space.

Por último, la otra forma de ver cómo está funcionando el Garbage Collection es viendo el LOG:


13.373: [GC 13.373: [ParNew: 96871K->11646K(118016K), 0.1215535 secs] 96871K->73088K(511232K), 0.1216535 secs] [Times
: user=0.11 sys=0.07, real=0.12 secs]
16.267: [GC 16.267: [ParNew: 111290K->11461K(118016K), 0.1581621 secs] 172732K->166597K(511232K), 0.1582428 secs] [Ti
mes: user=0.16 sys=0.08, real=0.16 secs]
19.177: [GC 19.177: [ParNew: 107162K->10546K(118016K), 0.1494799 secs] 262297K->257845K(511232K), 0.1495659 secs] [Ti
mes: user=0.15 sys=0.07, real=0.15 secs]
19.331: [GC [1 CMS-initial-mark: 247299K(393216K)] 268085K(511232K), 0.0007000 secs] [Times: user=0.00 sys=0.00, real
=0.00 secs]
19.332: [CMS-concurrent-mark-start]
19.355: [CMS-concurrent-mark: 0.023/0.023 secs] [Times: user=0.01 sys=0.01, real=0.02 secs]
19.355: [CMS-concurrent-preclean-start]
19.356: [CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
19.356: [CMS-concurrent-abortable-preclean-start]
 CMS: abort preclean due to time 24.417: [CMS-concurrent-abortable-preclean: 0.050/5.061 secs] [Times: user=0.10 sys=
0.01, real=5.06 secs]
24.417: [GC[YG occupancy: 23579 K (118016 K)]24.417: [Rescan (parallel) , 0.0015049 secs]24.419: [weak refs processin
g, 0.0000064 secs] [1 CMS-remark: 247299K(393216K)] 270878K(511232K), 0.0016149 secs] [Times: user=0.00 sys=0.00, rea
l=0.00 secs]
24.419: [CMS-concurrent-sweep-start]
24.420: [CMS-concurrent-sweep: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
24.420: [CMS-concurrent-reset-start]
24.422: [CMS-concurrent-reset: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
24.711: [GC [1 CMS-initial-mark: 247298K(393216K)] 291358K(511232K), 0.0017944 secs] [Times: user=0.00 sys=0.00, real
=0.01 secs]
24.713: [CMS-concurrent-mark-start]
24.755: [CMS-concurrent-mark: 0.040/0.043 secs] [Times: user=0.08 sys=0.00, real=0.04 secs]
24.755: [CMS-concurrent-preclean-start]
24.756: [CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
24.756: [CMS-concurrent-abortable-preclean-start]
25.882: [GC 25.882: [ParNew: 105499K->10319K(118016K), 0.1209086 secs] 352798K->329314K(511232K), 0.1209842 secs] [Ti
mes: user=0.12 sys=0.06, real=0.12 secs]
26.711: [CMS-concurrent-abortable-preclean: 0.018/1.955 secs] [Times: user=0.22 sys=0.06, real=1.95 secs]
26.711: [GC[YG occupancy: 72983 K (118016 K)]26.711: [Rescan (parallel) , 0.0008802 secs]26.712: [weak refs processin
g, 0.0000046 secs] [1 CMS-remark: 318994K(393216K)] 391978K(511232K), 0.0009480 secs] [Times: user=0.00 sys=0.00, rea
l=0.01 secs]


En este ejemplo se puede ver lo que está sucediendo en la JVM y se aprecia el algoritmo utilizado (CMS – Concurrent Mark Sweep Compact Collection). También se puede ver cuánto tiene el Eden ocupado, en este caso marcado como YG.