Home > 3rd Party Software, Desarrollo, Librerías/Frameworks > Encontrando la causa de los memory leaks

Encontrando la causa de los memory leaks

Artículo reeditado, proporcionando información mas detallada y ejemplo de aplicación que genera un ClassLoader Memory Leak, como

https://tododev.wordpress.com/2012/02/16/entender-detectar-y-localizar-memory-leaks-en-aplicaciones-web-java-lang-outofmemoryerror/

A continuación el artículo original.

Que levante la mano el que no haya tenido alguna vez un leak en Java. Sobre todo, el memory leak de classloaders que se da en el Tomcat, debido a la estrategia que usa con los classloaders al hacer un deploy o undeploy de una aplicación. Como no noto el olor a humanidad, creo que deberías seguir leyendo.

Comprobando si la aplicación tiene memory leaks

Es relativamente fácil saber si una aplicación tiene un leak o no. Relativamente. Basta con hacer un heap dump de la JVM en la que está corriendo el Tomcat y echar un vistazo a los classloaders. Esto lo podemos hacer fácilmente, por ejemplo, usando VisualVM:

  • Creamos un heap dump de la JVM en la que está corriendo Tomcat una vez hayamos hecho el undeploy de la aplicación.
  • Pulsamos el botón Classes en la pestaña del heap que habrá aparecido.
  • En la parte inferior se pueden introducir parámetros de búsqueda. Pulsando el botón a la izquierda seleccionamos el modo de búsqueda Contains e introducimos webappclassloader como término de búsqueda.
  • Haciendo doble click sobre  dicha clase aparecerán todas las instancias de la misma. Debería haber una por cada aplicación desplegada en Tomcat.
  • Seleccionamos cada instancia de la clase WebappClassLoader para consultar los miembros.

Si encontramos alguna instancia con el campo booleano started a false, es un indicio de que tenemos un leak de classloaders (a no ser que tengamos aplicaciones detenidas intencionadamente). Es decir, que alguna clase externa a nuestra aplicación mantiene una referencia a alguna clase de la aplicación, de manera que el GC, al encontrar referencias a dicha clase, no la elimine. Esto provoca que tampoco elimine el classloader, que mantiene referencias a todas las clases de la aplicación. Así que, dependiendo de la aplicación,  el leak podría ser bastante importante.

Una forma de tener algo mas de seguridad sobre si realmente es un leak o no es echar un vistazo al Vector classes, donde aparecen las clases cargadas por el classloader correspondiente, y ver si se corresponden con las de nuestra aplicación.

Cómo aparecen este tipo de  memory leaks  (los de classloader) en los contenedores de Servlets se explica bastante bien en esta entrada del blog de  Frank Kieviet, un nota de Oracle (supongo que de Sun->Oracle):

http://blogs.oracle.com/fkieviet/entry/classloader_leaks_the_dreaded_java

En este punto, hemos confirmado que nuestra aplicación tiene un leak. Porque saber, ya lo sabíamos, o no estaríamos montando este pollo. Ahora debería ser fácil encontrar la causa y resolverlo, no ?

No parece un leak

La excepción OutOfMemoryError simplemente indica que no hay memoria suficiente. Esto no quiere decir que la aplicación tenga un leak. De hecho, si la excepción no se produce tras una secuencia de undeploy/deploy, lo mas probable es que no sea un leak de classloaders (aunque podría ser de otro tipo).

Si no es un leak, posiblemente el problema se solucione modificando los parámetros de arranque de Tomcat (setenv.sh/setenv.bat). Esta excepción puede presentarse por varios motivos, los mas usuales son:

  • No queda memoria en el heap: el sistema no puede reservar mas memoria para nuevas instancias. Se puede solucionar modificando el tamaño del heap con las opciones -Xmx y -Xms.
  • No queda memoria en el PermGen: el sistema no puede cargar mas clases. Se puede solucionar modificando el tamaño del PermGen (por defecto es 64m, es decir, 64 MB, en las VM de Sun->Oracle) con la opción -XX:MaxPermSize.

Encontrando la causa del memory leak

Encontrar la causa del memory leak es fácil. Usando el visualVM, o cualquier otro profiler, hay que encontrar la clase que mantiene alguna referencia a alguna de las clases a las que el classloader localizado en el paso anterior tiene referencias (es decir, clases y librerías de nuestra aplicación). Bueno, es fácil de decir, pero un trabajo de chinos.

Afortunadamente, tenemos jhat. Viene incluido en el JDK6, pero debemos usar una versión relativamente reciente. La ventaja del jhat es que permite navegar por los paths de referencias, lo que hace mas sencillo identificar la causa del memory leak.

Por lo tanto, podemos enunciar los pasos a seguir para localizar dicha causa. Son lo siguientes:

  • Arrancar jhat: jhat  -J-Xmx512m heapdump-XXXXXXXX.hprof. Donde el archivo con extensión .hprof es el heapdump generado con visualVM.
  • Abrir la url http://localhost:7000 en un navegador web. Veremos una lista con todas las clases presentes en el dump.
  • Buscar alguna de las clases de nuestra aplicación, que no deberían estar ahí si hubieran sido eliminadas por el GC.
  • Seleccionar el link del ClassLoader (Loader Detail / ClassLoader), que nos llevará a la instancia del ClassLoader que mantiene alguna referencia a nuestra clase.
  • Seleccionar Chains from Rootset / Exclude weak refs. Nos aparecen todas las clases que contienen al classloader seleccionado en el path de sus referencias !!

Ya sabemos qué clases mantienen referencias a nuestro classloader. No deberían ser muchas, porque entonces tendríamos varias causas distintas para el leak. Ahora es cuestión de investigar por qué mantienen dichas referencias y solucionarlo. Muchas veces la solución pasa por añadir código que “haga limpieza” en el método contextDestroyed del ServletContextListener de nuestra aplicación, sobre todo cuando el origen del problema se localiza en una librería externa.

Esto viene también, muchísimo mejor explicado, en otra entrada del blog del mismo nota:

http://blogs.oracle.com/fkieviet/entry/how_to_fix_the_dreaded

Nota: Es posible que jhat también esté en versiones anteriores del JDK, pero no nos serviría para nuestro propósito, puesto que dichas versiones tienen una limitación que no permite encontrar el path completo de referencias.

Y ahora, qué ?

Ahora, bájate todo el código de SVN y aplica estos sencillos pasos a todas las aplicaciones que encuentres para encontrar la causa de los leaks. Porque haberlos, estamos seguros de que hay.

Ya estás tardando !!!!

Advertisements
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

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

WordPress.com Logo

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

Twitter picture

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

Facebook photo

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

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: