La temida excepción “java.lang.OutOfMemoryError: PermGen space failure”

11/02/2008

Java Garbage

¿Alguna vez te has encontrado con un error java.lang.OutOfMemoryError: PermGen space failure al re-desplegar tu aplicación en un servidor de aplicaciones? Acusaste al servidor de aplicaciones, mientras lo reiniciabas, para continuar con tu trabajo pensando que claramente se trata de un bug en el servidor de aplicaciones. Esos desarrolladores de servidores de aplicaciones deberían metérselo por donde les quepa, ¿o no? Bueno, quizás. Pero quizás ¡la culpa sea tuya!.

Veamos el siguiente ejemplo de un, aparentemente, inocente servlet.

package com.stc.test;

import java.io.*;
import java.util.logging.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class MyServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Log at a custom level
Level customLevel = new Level(“OOPS”, 555) {};
Logger.getLogger(“test”).log(customLevel, “doGet() called”);
}
}

Intenta re-desplegar este pequeño ejemplo varias veces. Apuesto a que en algún momento fallará con el temido error java.lang.OutOfMemoryError: PermGen space failure. Si quieres saber que está pasando continua leyendo.

Analizando el problema

Los servidores de aplicaciones como Glassfish te permiten crear una aplicación (.ear, .war, etc) y desplegarla junto con otras aplicaciones en este servidor de aplicaciones. Si ver necesario realizar un cambio en tu aplicación, sencillamente puedes realizar el cambio en tu código fuente, compilarlo, y re-desplegar la aplicación sin afectar al resto de aplicaciones que aún se están ejecutando en el servidor: no necesitas re-iniciar el servidor de aplicaciones. Este mecanismo funciona bien en Glassfish y otros servidores de aplicaciones.

El modo en que esto funciona es que cada aplicación se carga usando su propio classloader (cargador de clases). Esto es, un classloader es una clases que carga archivos .class. Cuando eliminas la aplicación, el classloader se descarta y él y todas las clases que ha cargado, deberían ser recolectadas como basuara entes o después.

De alguna forma, sin embargo algo podría hacer que se mantuviera el classloader, y evitar que fuese recolectado como basura. Y eso es lo que está causando la escepción “java.java.lang.OutOfMemoryError: PermGen space”.

Espacio PermGen

¿Y qué es el espacio PermGen? La memoria de la Máquina Virtual se divide en varias regiones. Una de estas regiones es el PermGen. Es un area de memoria utilizada para (entre otras cosas) cargar archivos class (y ojo que son clases y no objetos -o instancias de dichas clases-). El tamaño de esta región de memoria es fijo, esto es, no cambia mientras la VM se esté ejecutando. Podemos especificar el tamaño de esta región con un parámetro de línea de comandos: -XX:MaxPermSize=XXXm . El valor por defecto es 64 Mb en la VMs de Sun.

Si hay algún problema con las clases que son recolectadas como basura y continuamos cargando clases, la VM se quedará sin espacio en esta región de memoria, incluso si hay un montón de memoria disponible en el heap. Fijar el parámetro -Xmx no ayudará: este parámetro solo especifica el tamaño total del heap y no afecta al tamapo de la región PermGen.

Recolección de basura y cargadores de clases

Cuando escribes algo tan estúpido como:

private void x1() {
for (;;) {
List c = new ArrayList();
}
}

estás creando objetos continuamente; sin embargo el programa no se queda sin memoria: los objetos que creas son recolectados como basura y por tanto liberando el espacio de memoria de modo que puedas coloar otros objetos. Un objeto solamente puede ser recolectado como basura si el objeto es “inalcanzable”. Lo que esto quiere decir es que no hay modo de acceder al mismo desde ninguna parte del programa. Si nadie puede acceder al objeto, no tiene sentido mantenerlo, y por tanto puede ser recolectado como basura. Echémosle un ojo a la imagen de la memoria del servlet de ejemplo. Primero, simplifiquemos aún mas el este ejemplo:

package com.stc.test;

import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class Servlet1 extends HttpServlet {
private static final String STATICNAME = “Simple”;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
}

Tras cargar el servlet anterior, en memoria tenemos los siguientes objetos (por supuesto limitándonos a los relevantes):

Mapa de memoria del sencillo servlet

En la imagen podemos ver los objetos cargados por el classloader de la aplicación en amarillo, y el resto en verde. Mostramos un objeto contenedor simplificado que contiene referencias al classloader de la aplicación creado únicamente para esta aplicación, y a la instancia del servlet de modo que el contenedor pueda invocar su método doGet() cuando llega una petición. Nota que el objeto de clase posee un objeto STATICNAME. Otras cosas importantes a notar son:

  1. Como cada objeto, la instancia Servlet1 mantiene una referencia a su clase (Servlet1.class).
  2. Cada objeto class (ej. Serlvet1.class) mentiene una referencia al classloader que lo cargó.
  3. Cada classloader mantiene referencias a todas las clases que ha cargado.

La consecuencia importante de esto es que siempre que un objeto de fuera del AppClassloader tenga una referencia a un objeto cargado por el AppClassloader, ninguna de sus clases pude ser recolectada como basura.

Para ilustrar esto, veamos que ocurre cuando la aplicación se elimina (undeploy): el objeto Container pone a null sus referencias a la instancia Servlet1 y al objeto AppClassloader.

Mapa de memoria tras desinstalar la aplicación

Como podemos ver, ninguno de los objetos es alcanzable, de modo que todos pueden ser recolectados como basura. Veamos ahora que ocurre cuando usamos el ejemplo original donde utilizamos la clase Level:

package com.stc.test;

import java.io.*;
import java.net.*;
import java.util.logging.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class LeakServlet extends HttpServlet {
private static final String STATICNAME = “This leaks!”;
private static final Level CUSTOMLEVEL = new Level(“test”, 550) {}; // anon class!

protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Logger.getLogger(“test”).log(CUSTOMLEVEL, “doGet called”);
}
}

Fíjate en que la clases CUSMTOMLEVEL es una clases anónima. Esto es necesario porque el constructor de Level es protegido. Echemos un ojo a la imagen de memoria de este escenario:

Mapa de memoria del servlet original

En la imagen podemos ver algo no esperado: la clase Level contiene un miembro estático de todos los objetos Level que han sido creados. Este es el constructor de la clase Level en el JDK:

protected Level(String name, int value) {
this.name = name;
this.value = value;
synchronized (Level.class) {
known.add(this);
}
}

Aquí known es un ArrayList estático en la clase Level. Ahora, ¿qué ocurre si la aplicación es desinstalada?

Mapa de memoria tras desintalar el servlet original

Solamente el objeto LeakServlet puede ser recolectado como basura. Debido a la referencia del objeto CUSTOMLEVEL fuera del AppClassloader, el objeto de la clases anónima CUSTOMLEVEL (LeadServlet$1.class) no se puede recolectar como basura, y por tanto tampoco puede serlo el AppClassloader, y por tanto no puede ser recolectada como basura ninguna de las clases que cargó el AppClassloader.

Conclusión: cualquier referencia desde fuera de la aplicación a un objeto de la aplicación en la cual haya sido cargada la clase por el cargador de clases de la aplicación, causará una fuga de memoria en el cargador de clases.

vía

Algunos enlaces de interés

Java JVM GC, PermGen, and Memory Options
Preventing Java’s java.lang.OutOfMemoryError: PermGen space failure
Java Permgen space, String.intern, XML parsing 10
Good Riddance, PermGen OutOfMemoryError !
Understanding and avoiding the Java Permgen Space error

21 Responses to “La temida excepción “java.lang.OutOfMemoryError: PermGen space failure””

  1. felix Says:

    Gracias por el articulo,
    es una magnífica explicación de uno de los problemas mas frustrantes de Java.

  2. BJ Says:

    Gracias por la iformacion.Mucho no entendi porque no tengo mucha experiencia .Me gustaria saber como solucionarlo ya que trabajo con el servidor Tomcat 5.5 y me esta dado el error. Muchas Gracias

  3. Toli Says:

    Excelente!

    Muchas gracias por esta magnífica explicación.

  4. Floshton Says:

    Muy buen artículo. Aclaró muy bien mi duda.Gracias

  5. Rene Says:

    thnx

  6. Luz Says:

    Umm esto me está sucediendo con una tarea y ya me tiene de las mechas. Entendí poco en realidad, pero em guastaria más bien como consejos de cuáles son las cosas que debemos evitar hacer para llegar a esto, qué cosas debemos minimizar??

  7. DaniloGhost Says:

    Excelente articulo, ahora que entiendo el problema a buscar la solucion…xD

  8. Andres Says:

    Muchas gracias, muy bien explicado, ahora solo falta que se lo lean los desarrolladores.

  9. Ragnar Says:

    Excelente artículo. Cristalino.

    Gracias por publicarlo.

  10. Vicente Says:

    Y esto no se podría considerar un bug de la VM?

  11. ClonyManiatico Says:

    Te agradesco Infinitamente

    Agregue la siguiente linea en las propiedades generales de la jvm (Servidores de aplicaciones > Svr_Aplicacion > Definición de proceso > Máquina virtual Java) y dejo de pasmarse la aplicacion, el error hasta el momento ya no salio

    -XX:MaxPermSize=256m

  12. Jorge Says:

    Hola, gracias por la aclaración. Ahora que lo entiendo esperaré a ver como lo soluciono.

  13. EDWIN Says:

    La pregunta es que se debe de hacer para corregir en la aplicacion

  14. J-unkie Says:

    Excelente. Muy bien explicado, y muy útil. Es una de las cosas que a veces por ir demasiado atareados no nos paramos a intentar comprender.

    Sergio.

  15. Acorayda Says:

    Por mucho que aga todo esto , no sirve de nada .
    Si pudieran ayudarme

  16. enric Says:

    este articulo es una traduccion del articulo original.

    tengo una duda, ¿el sentido de la referencia en rojo no deberia ser al reves? es decir, como yo lo veo, el objeto CUSTOMLEVEL hace referencia a la clase LEVEL.


  17. […] Ésta web y ésta otra lo explican bastante bien, pero resumiendo, el problema viene de que el ClassLoader del anterior de ploy de la aplicación no ha sido descargado y recolectado como basura, por lo que se lanza ésa excepción. […]


  18. […] Si has usado el metodo String.intern() seguro que te suena el “java.lang.OutOfMemoryError: PermGen space” [1] […]

  19. rpau Says:

    Gracias🙂


Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: