Cómo reducir el consumo de memoria de una aplicación desde 1.5GB a 73MB
En este blog post Nikita Salnikov-Tarnovski presenta un interesante caso de estudio de una aplicación Java que inicialmente tenía un heap de 1.5GB y que terminaron reduciendo a 73MB. El primer error grande que cometieron en la aplicación es trabajar directamente con objetos creados a través de XMLBean, que resultaron emplear internamente estructuras de datos más compleja de lo necesario. Almacenando manualmente estos objetos en java.util.HashMap la aplicación pasó de consumir 1.5 GB derrama a 214 MB.
Después cambiaron el java.util.HashMap por una implementación más eficiente de Trove Collections, lo que bajó el consumo de memoria 143 MB. Haciendo cambios en los propios objetos que estaban almacenando para representar de un modo más eficiente la información bajaron a 93 MB. Finalmente, estaban usando una máquina virtual de 64 bits. Activando la opción para comprimir punteros pasaron a emplear 73 MB.
Aquí tenéis un gráfico que muestra cómo fue evolucionando su gasto de memoria según fueron realizando los diferentes cambios en la aplicación:
Se trata de un caso de estudio bastante interesante del cual podemos aprender lecciones para nuestras aplicaciones.
Reader Comments (3)
Yo he vivido un caso mas pequeño con itext y el uso de ByteArrayOutputStream que resulta clonar el array de bytes al obtenerlo doblando el uso de memoria respecto al uso de un array de bytes estándar.
Por estas cosas es conveniente volver a pensar las cosas de cara a la optimización, a veces lo mas sencillo puede ser también mas óptimo.
Bueno, creo que lo verdaderamente impresionante de este análisis es la ineficiencia de XMLBeans
De 1.5 GB a 214 MB es para abrir un bug crítico en el proyecto Apache XMLBeans
aunque tampoco precisa qué versión usan
De todas formas, JDK 6 incluye una implementión de JAXB ¿ quien necesita XMLBean ?
En mi trabajo nos ha pasado algo parecido con el código entregado por un proveedor. Aparte de los típicos memory leaks (que acaban por generar caídas del servidor con típicos mensajes de errores por memoria agotada) habían caído en una tentadora opción:
Codificar todos los campos como Strings, independientemente de que fuesen booleanos, números o chars. Daba igual, todo se guardaba como un String.
¿Las consecuencias?
* Consumir 3 veces más memoria por cada objeto que con una representación más adecuada.
* Pérdida de rendimiento porque el Garbage realiza mucho más trabajo (menos memoria para trabajar implica que se llena con mayor frecuencia). Al poco tiempo se provocaba el colapso del servidor porque el Garbage no podía liberar la memoria suficiente para trabajar durante un rato.
* Caídas por PermGemOutOfMemoryError y OutOfMemoryError.
Quizá merecería la pena escribir un artículo sobre depuración de memoria, algo en lo que me he visto envuelto estas últimas semanas.