Memoria PermGen y ClassLoader

Lunes,2 febrero, 2009 at 1:36 7 comentarios

Quién no se ha encontrado alguna vez con la temida excepción ‘java.lang.OutOfMemoryError: PermGen space failure’. Esta excepción es debida a que la máquina virtual de Java(JVM) se ha quedado sin memoria PermGen.

¿Cómo se distribuye la memoria en la JVM?

En la JVM podemos diferenciar entre tres tipos de memorias:

  1. Memoria de almacenamiento dinámico o memoria Heap: Esta zona de la memoria es la encargada de almacenar las instancias de los objetos creados por las aplicaciones.
  2. Memoria de almacenamiento estático o memoria PermGen: En esta zona de la memoria se almacenan las clases que han sido cargadas por la aplicación. Esta zona 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 [1].
    • Array de Objetos y array de tipos asociados con una clase (Ej: Array de objetos con referencia a métodos).
    • 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’. Éstas se almacenan para la optimización en el acceso a las mismas.
  3. Memoria dinámica nativa: Es la zona de la memoria encargada de almacenar 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 nuestro casos nos referiremos a la Memoria PermGen que es la que genera la excepción anteriormente indicada.

¿Qué puede  causar que se produzca esta excepción?

  1. Nuestra 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:
    • Cuando se crea un objeto relativo a una clase, es decir, llamada mediante el operador new.
    • Cuando accedemos a algún método o atributo estático de la clase.
  2. 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.

  3. Nuestra aplicación utiliza muchas cadenas ‘internas’: String.intern [2] 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.
  4. 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 );
  5. 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. Este caso se encuentra perfectamente explicado aquí [3].

¿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

Referencias:

[1] http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#20080

[2] http://java.sun.com/javase/6/docs/api/java/lang/String.html#intern()

[3] http://rubensa.wordpress.com/2008/02/11/java-classloader-permgen-exception/

[4] http://my.opera.com/karmazilla/blog/index.dml/tag/permgen

Entrada archivada en:Java. Etiquetas:, , .

Codificación en BD (MySQL y Oracle) Comenzando en el Software Libre

7 comentarios Añade el tuyo

  • 1. Manuel Jesús Recena Soto  |  Lunes,2 febrero, 2009 a las 10:14

    Hola José:

    Muy interesante. Es algo que pocos desarrolladores tienen en cuenta, y mucho menos, administradores de sistemas encargados de realizar despliegues en producción.

    Si estás trabajando con aplicaciones web y Tomcat:
    http://www.manuelrecena.com/blog/archives/241

    Y en general:
    http://amunizmartin.wordpress.com/2008/08/26/memoria-permgen-en-java/

    Un saludo

  • 2. José Morales  |  Lunes,2 febrero, 2009 a las 16:02

    Efectivamente. Es un tema que pocas veces es tenido en cuenta.

    En estos enlaces se indican un conjunto de herramientas muy útiles para el análisis de las aplicaciones y el uso de la memoria que realizan las mismas.

  • 3. Antonio Manuel Muñiz Martín  |  Lunes,2 febrero, 2009 a las 23:07

    Hola Jose.

    Las veces que he visto esta excepción ha sido provocada por Hibernate, al parecer usa una gran cantidad de memoria PermGen.

    Resolvimos este problema con -XX:MaxPermSize=256m (como tú indicas). El resto de opciones nunca las hemos usado. Esta bien conocerlas.

    Un saludo.

  • 4. José Morales  |  Lunes,2 febrero, 2009 a las 23:40

    Como indicas, el uso de Hibernate puede se un aliciente para que se produzca este error.

    En el caso de Hibernate puede ser debido a la utilización de JavaAssist. Este optimizador genera un conjunto de clases para la optimización de Hibernate. Por lo que he podido ver este problema es bastante usual.

  • 5. Adam M. Gamboa Gonzalez  |  Miércoles,14 julio, 2010 a las 6:22

    Me gustaria contribuir con el caso del permgen space causado por el javassist, con una solucion dada en el foro: http://rubensa.wordpress.com/2008/02/11/java-classloader-permgen-exception/

    En donde se indica que se puede solucionar agregando la siguiente linea en el hibernate.properties el valor de:
    “hibernate.bytecode.provider javassist” a “hibernate.bytecode.provider cglib”.

    en caso de utilizar JPA, cambiar en el persistence.xml poner en dicha propieda el siguiente valor:

    Lo que se indica es que se cambie la generacion de bytecode de hibernate, de la javassist (por defecto y que provoca el permgen space) a cglib…

    Espero les funcione…

  • 6. José Morales  |  Miércoles,14 julio, 2010 a las 8:34

    Muchas gracias por el comentario. He estado mirando un poco sobre los dos providers y la verdad es que parece que cglib es una solución más rápida y que consume menos recursos que javassist.

    A ver si lo pruebo en el próximo proyecto.

  • 7. Steven Vera  |  Martes,18 enero, 2011 a las 11:05

    Buenas José,

    he revisado todo lo que comentas de las distintas memorias y me parece muy útil lo que comentas, pero tengo un problema relacionado con lo que comentas de “SAXParser”.. te pongo en situación:

    he desarrollado una aplicación en la que usa este pharser para poder validar xml´s formados por la aplicación… este parser, sale al exterior(a otra máquina) para poder recuperar el “.xsd” contra el que tiene que validar dicho xml creado… el problema que se esta produciendo es a la hora de ejecutar la aplicación de manera concurrente…. aunque cada ejecución es independiente una de otra, y no se ven, el problema del parser es que nos dice que no existen bloques de información en el xml creado… y en realidad si que existen,,,, no tenía ni idea de nada hasta que he visto tu post sobre la memoria… me podrías indicar si con la subida de memoria sería necesario??… o por el contrario, hay algo que no tengo en cuenta y por eso no se ejecuta bien el parser??…

    Muchas Gracias por todo y disculpa las molestias.

    Un Saludo.

Deja un comentario

Fill in your details below or click an icon to log in:

Logo de WordPress.com

You are commenting using your WordPress.com account. Log Out / Cambiar )

Twitter picture

You are commenting using your Twitter account. Log Out / Cambiar )

Facebook photo

You are commenting using your Facebook account. Log Out / Cambiar )

Connecting to %s

Trackback este articulo  |  Suscríbete a los comentarios vía RSS Feed


Feeds

Entradas Recientes

Categorias


Seguir

Get every new post delivered to your Inbox.