Mostrando entradas con la etiqueta JVM. Mostrar todas las entradas
Mostrando entradas con la etiqueta JVM. Mostrar todas las entradas

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.

jueves, 27 de noviembre de 2014

Tuneando la JVM - Argumentos de memoria

Parámetros de la JVM

En la siguiente imágenes podemos ver los parámetros opcionales que se utilizan en la máquina virtual de java.


Argumentos más utilizados


-Xms Este valor indica la cantidad de memoria asignada al iniciar la máquina virtual de java. Mi recomendación para el valor de este argumento es que corresponda a la mitad Xmx o en el caso de servidores al mismo valor que el Xmx.

-Xmx Este valor indica la memoria máxima que podrá alcanzar la maquina virtual de java. Un valor bajo de este argumento puede hacer que la ejecución del código en la JVM lance una excepción de tipo java.lang.OutOfMemoryError, aumentando este argumento podemos evitar este problema.

-Xss Este valor indica la cantidad de memoria de pila por hilo de ejecución. Este parámetro por defecto suele estar en 512k, dependiendo del Sistema operativo y arquitectura del mismo. Cuando se producen excepciones del tipo java.lang.StackOverflowError es recomendable ajustar una mayor memoria de pila por hilo. Un valor muy alto puede ser contraproducente, puesto que el sistema se puede quedar sin memoria para crear nuevos hilos de ejecución.

-Xmn Este valor controla la cantidad de espacio del almacenamiento dinámico que puede consumir la generación joven (llamado Eden), un valor bajo indica que el Garbage Collector se ejecute muchas veces sin tener realmente nada que limpiar y un valor muy alto puede hacer que los objetos basura salgan de la memoria "Eden" de la JVM y se introduzcan en la memoria de generación vieja.

-XX:MaxPermSize Este argumento indica la cantidad de memoria Non-Heap/perm máxima, esta memoria se utiliza para cargar parte de código que utilizara la JVM. Aumentando este parámetro conseguiremos evitar que se produzcan excepciones del tipo java.lang.OutOfMemoryError: PermGen space.

Cómo solventar un java.lang.OutOfMemoryError

Alguna vez te has encontrado la excepción "java.lang.OutOfMemoryError: PermGen space failure". Esta excepción indica que la máquina virtual de Java (JVM) se ha quedado sin memoria PermGen.

¿Cuales son los tipos de memoria que encontramos en la JVM?


En la JVM se pueden diferenciar 3 tipos de memorias:

  1. Memoria de almacenamiento dinámico o memoria Heap: Se almacenan las instancias de los objetos creados por las aplicaciones. 
  2. Memoria de almacenamiento estático o memoria PermGen: Se almacenan las clases que han sido cargadas por la aplicación. También, es utilizada para almacenar la información para la optimización de la aplicación por parte de la JVM. Los elementos que se almacenan en esta zona de la memoria son:
    • Métodos de las clases (incluido bytecode).
    • Nombre de las clases.
    • Pool de Constantes de la JVM.
    • Array de Objetos y array de tipos asociados con una clase.
    • Objetos internos creados por la JVM (java.lang.Object or java.lang.Exception).
    • Información utilizada para la optimización de los compiladores.
    • Cadenas internas. Estas se almacenan para la optimización en el acceso a las mismas.
  3. Memoria dinámica nativa: Se almacena el código de la Interfaz Nativa de Java (JNI) o la bibliotecas nativas de una aplicación y en la puesta en práctica de la JVM asignan memoria del almacenamiento dinámico nativo.



En este artículo, nos referiremos sólo a la Memoria PermGen que es la que genera la excepción anteriormente indicada.

¿Qué causa hace que salte esta excepción?


  • La aplicación carga demasiadas clases: A la hora de cargar las clases, la JVM lo hace bajo demanda. En la práctica esto se transforma en dos posibles casos:
    1. Cuando se crea un objeto relativo a una clase, es decir, llamada mediante el operador new.
    2. Cuando accedemos a algún método o atributo estático de la clase.
Hay que decir que una vez que una clase es cargada en el ClassLoader no será descargada del mismo y a su vez se mantendrá una referencia desde el ClassLoader a la Clase y desde la clase al ClassLoader que la ha cargado. Todo esto será almacenado en la memoria PermGen, de tal forma que conforme vamos accediendo a las diferentes clases que son utilizadas por nuestra aplicación aumentará el tamaño ocupado.
NOTA: Este caso puede ser especialmente dado en los JSP, ya que cada uno de ellos será convertido en un fichero de una clase Servlet. Otros casos se pueden dar en algunos frameworks como Hibernate, EJB … Debido a que estos frameworks generan clases auxiliares para la optimización de los mismos.
  • La aplicación utiliza muchas cadenas ‘internas’: String.intern es una optimización de la JVM para permitir optimizar la comparación entre cadenas. La comparación carácter a carácter entre cadenas es muy lenta, por ello se mantiene un lista de cadenas instanciadas, de tal forma que permite una comparación de identidades que es más rápida. El problema de esto es que la lista utilizada para la comparación de cadenas es establecida en la memoria PermGen y en algunos casos el tamaño de la misma puede ser demasiado elevado. 
Un ejemplo de este caso se da en algunos parses de XML, los cuales genera cadenas ‘internas’ muy grandes, por ello en algunos casos pueden provocar este tipo de excepción.
Document doc = SAXParser.new().parse( cadenaXML );

  • Fugas de Memoria (Memory Leaks): Esto se produce cuando re-desplegamos una aplicación. En los servidores de aplicaciones se suele cargar el ClassLoader de cada una de las aplicaciones que se cargan. Si cuando re-desplegamos la aplicación no se elimina de forma correcta el ClassLoader anterior, este se mantendrá en la memoria PermGen y se sumará al espacio ocupado por la nueva aplicación. Cuando re-desplegamos un par de veces la aplicación, provocará que agotemos la memoria PermGen.


¿Cómo solucionarlo?


Un primera opción sería modificar nuestro código para evitar el llegar a alguno de los tres posibles casos. Esta opción muchas veces no es válida debido a que dependemos de terceros o no podemos modificar el código de nuestra aplicación. La segunda opción sería modificar los parámetros de la JVM.

A continuación detallo un conjunto de parámetros que pueden ser indicados a la JVM para paliar, en la medida de lo posible, la aparición de esta excepción:


  • Aumentar el tamaño de la memoria PermGen. Ésta suele ser la primera medida a tomar, aunque no siempre resuelve los problemas sino que los aplaza y a veces puede derivar en problemas con el colector de basura o en problemas con la escasez de memoria dinámica.
-XX:MaxPermSize=128m
NOTA: Por defecto el tamaño de la memoria PermGen es de 64MB.


  • En el caso de multiprocesadores podemos establecer un colector de basura concurrente, el cual puede ‘eliminar’ memoria PermGen, cosa que el colector por defecto no puede. Para ello primero cambiamos el algoritmo del colector de basura:
-XX:+UseConcMarkSweepGC


  • Habilitamos en la JVM la posibilidad de ‘eliminar’ memoria PermGen:
 -XX:+CMSPermGenSweepingEnabled


  • Las clases son un caso particular a la hora de gestionarlas en la memoria PermGen, por ello debemos indicarle a la JVM la posibilidad de permitir que descargue las clases:
-XX:+CMSClassUnloadingEnabled