martes, 2 de diciembre de 2014

Pasos para solventar un OutOfMemoryError: Permgen space

Las aplicaciones Java se encuentran autorizadas a utilizar sólo una cantidad limitada de memoria. La cantidad exacta de memoria que usará tu aplicación en particular debe ser especificada durante el inicio de la aplicación. Haciendo las cosas más complejas, la memoria Java es separada en diferentes regiones tal y como se puede observar en el siguiente diagrama:


El tamaño de todas estas regiones, incluyendo el área PermGen, se establece durante el lanzamiento de la JVM. Si tu en el inicio no estableces el tamaño, se utilizarán los valores predeterminados que usa la plataforma.

Entonces, si te aparece: "java.lang.OutOfMemoryError PermGen space" indicará que el tamaño permanente del área en memoria se ha agotado.

Ejemplo de java.lang.OutOfMemoryError


El espacio PermGen usado está fuertemente correlacionado con el número de clases que se cargan el la JVM. El siguiente código sirve de ejemplo para obtener dicho mensaje:

import javassist.ClassPool;

public class MicroGenerator {

 public static void main(String[] args) throws Exception {
  for (int i = 0; i < 100_000_000; i++) {
   generate("eu.plumbr.demo.Generated" + i);
  }
 }

 public static Class generate(String name) throws Exception {
  ClassPool pool = ClassPool.getDefault();
  return pool.makeClass(name).toClass();
 }
}

En este ejemplo, el código fuente esta iterando sobre un loop y generando clases en tiempo de ejecución. La complejidad de la generación de claes es atendida por la libreria javassit. Al lanzar el código anterior, se mantendrá la creación de nuevas clases y esto hará que se carguen las definiciones en el espacio Permgen hasta que llegue al límite y de esta forma se lanzará el error "java.lang.OutOfMemoryError: Permgen space" 

Solución para java.lang.OutOfMemoryError


La primera solución a la OutOfMemoryError debido a PermGen es obvia. Si hemos agotado el espacio de memoria para PermGen tenemos que aumentar su tamaño. Esta solución es realmente útil si sólo no se ha definido en el inicio de la JVM espacio suficiente. Así que se puede modificar su configuración en el lanzamiento de aplicaciones y agregar (o aumentar en caso de que exista) lo siguiente:

 -XX:MaxPermSize=512m

 Este parámetro de configuración indica a la JVM que debe permitir al espacio PermGen crecer hasta 512 MB antes de lanzar una OutOfMemoryError.


La segunda posibilidad es permitir al GarbageCollector (GC) descargar las clases de PermGen. La JVM estándar tiene un estilo conservador, toda clase que nace debe existir para siempre. Así una vez cargadas, las clases permanecen en la memoria incluso aunque no se vuelva usar jamás.

 Esto puede convertirse en un problema cuando la aplicación está creando un montón de clases de forma dinámica y las clases generadas no son necesarias por largos periodos de tiempo. En tal caso, permitir a la JVM descargar las clases puede ser de gran ayuda. Esto se logra agregando un nuevo parámetro de configuración en el inicio:

 -XX:+CMSClassUnloadingEnabled

Por defecto, se establece a false y para poder habilitarlo es necesario establecerlo de manera explícita siguiendo las opciones de Java Options.

Se habilita CMSClassUnloadingEnabled, la GC limpiará el PermGen y eliminará cualquier clase que no se utiliza. Tenga en cuenta, que esta opción sólo funciona cuando UseConcMarkSweepGC también se habilita.

 -XX:+UseConcMarkSweepGC

Advertencia: Antes de usar estos dos parámetros para proporcionar una solución rápida, ten encuentra que solamente estas enmascarando el error, lo que realmente importa es el detectar si las clases que se instancias deben estar o no y encontrar una solución a ello.

Por ejemplo, si realmente quieres arreglar la fuga en la memoria PermGen, podrías utilizar el siguiente Servlet Context Listener para detectar fallos:

// ServletContextListener
public class JdbcDriverLeakPreventer implements ServletContextListener {
  @Override
  public void contextInitialized(ServletContextEvent sce) {
    //Nothing to do
  }

  @Override
  public void contextDestroyed(ServletContextEvent sce) {
    ClassLoader applicationClassLoader = this.getClass().getClassLoader();
    Enumeration driverEnumeration = DriverManager.getDrivers();
    while (driverEnumeration.hasMoreElements()) {
      Driver driver = driverEnumeration.nextElement();
      ClassLoader driverClassLoader = driver.getClass().getClassLoader();
      if (driverClassLoader != null 
          && driverClassLoader.equals(applicationClassLoader)){
        try {
          DriverManager.deregisterDriver(driver);
        } catch (SQLException e) {
          e.printStackTrace(); //TODO Replace with your exception handling
        }
      }
    }
  }
}

Este Servlet Listener debe ser registrado en el web.xml de tu aplicación.

user.package.JdbcDriverLeakPreventer


Hay diferentes formas de investigar que ha ocupado espacio PermGen en su JVM y si los objetos tengan alguna razón válida para llenarlo.

2 comentarios:

  1. Otra opción: pasarse a Java8 donde ya por fin se han cargado el PermGen :-D (se utiliza el Heap para cargar las definiciones de clases)

    ResponderEliminar
  2. La verdad es un avance. Lamentablemente el proyecto en el cual me encuentro trabajando actualmente, apenas, esta migrando a Java 7. Por lo tanto, es bueno tener información de los errores del PermGen que van apareciendo en las diferentes fases del proyecto.

    ResponderEliminar