lunes, 23 de mayo de 2011

Problemas de memoria

Aunque también podría, no me refiero en este caso a mi escasa capacidad para almacenar recuerdos, sino a la gestión de memoria al trabajar con JPA.

En estos días estoy liado con el desarrollo de un proceso de migración que obtiene información de una base de datos Oracle, la procesa, y la escribe en ficheros de texto. El procesamiento principal se reducía a ir obteniendo todos los registros de una tabla, y tratarlos uno a uno. El volumen de datos estaba en torno a los 200.000 registros.

En sí, el código necesario para este trabajo no tiene mayor complejidad, puesto que no hay más que a ir sacando la información de la base de datos, y manejarla. El problema que se nos va a presentar tiene que ver con los límites de memoria de nuestro pc. Tal como funciona JPA, cada uno de los registros de la base de datos se convertirá en una instancia de la entidad con la que estemos trabajando que, lógicamente, se alojará en memoria. Y lo normal, con estos volúmenes de información, es que no tengamos capacidad para almacenarlos todos.

Al tratarse de un procesamiento iterativo, en el que cada objeto lo utilizamos independientemente del resto, y una vez que terminamos con él no lo necesitamos más, podemos pensar que no hay problema en que en cada momento haya un único objeto en memoria, que sea justamente el que estemos usando. El problema es que JPA no tiene conocimiento de esta situación, y cada uno de los objetos que se van cargando se quedan ahí a perpetuidad. Lógicamente en poco tiempo te encontrarás el temido mensaje:

java.lang.OutOfMemory - Java heap space

Java se ha quedado sin memoria. La solución directa no puede ser otra, por tanto, que aumentarla. Si no cabes en tu casa, búscate una más grande (o mayor, que diría mi hermana). Esto se puede hacer simplemente incluyendo el parámetro -xmx al lanzar tu aplicación. En jDeveloper, por ejemplo, esta opción la puedes incluir en la sección Java Application de las propiedades de tu proyecto.

Esta opción está muy bien, pero tiene pegas. Una importante es que si al final consigues solucionar el problema, estarás en cualquier caso utilizando mucha más memoria de la que realmente necesitas, malgastándola. Una todavía más determinante es que realmente la limitación sigue estando ahí: ¿qué haces si no tienes suficiente ni asignándole a Java toda la memoria de tu equipo? (Aparte de la opción de pasar por caja y ampliar la memoria, que sería una opción un tanto drástica).

Vamos, que mi opinión es que esta no es una buena solución para nuestro problema concreto. El aumento de memoria se debería hacer sólo si realmente necesitas tener toda esa información cargada. Si no, mejor buscar alternativas.

Y nuestra alternativa en este caso es clara. Sabemos que no necesitamos más que un objeto en memoria en cada iteración, así que digámosle a JPA que no los acumule tontamente.

Conforme vamos cargando entidades, estas pasan a ser gestionadas por JPA, componiendo lo que se conoce como Contexto de persistencia. Y mientras una entidad sea gestionada, seguirá estando ahí y no liberará memoria. Por eso existe la posibilidad de decirle a JPA que deje de gestionar entidades, utilizando el método clear. Al invocarlo, todas las entidades del contexto de persistencia dejarán de ser manejadas, y el recolector de basura de Java podrá por fin hacerse cargo de ellas. También existe la posibilidad de aplicar este tratamiento a una única instancia mediante el método detach.

Eso sí, tampoco hay necesidad de invocar al método clear cada vez que terminemos con una entidad. Lo ideal sería hacer el procesamiento por lotes, y liberar cada vez que se termine con uno.

No hay comentarios:

Publicar un comentario

Cualquier aportación será bienvenida