6.2.
Buena práctica número 16. Gestione las fugas de memoria
En
este documento, una fuga de memoria (“memory leak”) es
aquella memoria que la aplicación ya no necesita, pero que
el recolector de basura (“garbage collector”) no libera.
En sentido estricto, esto no es realmente una fuga: es simplemente
que el programador no da la información suficiente al recolector
de basura para que haga su trabajo.
La
recolección de basura en Java funciona identificando objetos
que son alcanzables por la máquina virtual de Java, como se
muestra en la figura 2. De forma periódica (por ejemplo, cuando
la cantidad usada de memoria sobrepasa un cierto límite),
la máquina virtual invoca al recolector de basura (GC). En
la ejecución del programa, algunos objetos referencian a otros,
creando un grafo dirigido. El recolector de basura examina todos
los objetos en memoria y ve si son alcanzables desde el objeto raíz,
es decir, si hay un camino en el grafo desde ese objeto raíz
a cada objeto. Con esta información, el recolector de basura
marca los objetos como alcanzables o no y libera todos los objetos
que no son alcanzables.
Figura
2. El recolector de basura de Java funciona determinando los objetos
que son alcanzables. Una flecha indica una referencia de un objeto
a otro.
Consecuentemente,
si se ha dejado una referencia a un objeto en algún lugar,
el objeto no puede ser liberado, pues puede ser alcanzado de alguna
forma. Las fugas de memoria pueden ocurrir de muchas formas diferentes,
pero sus causas son solamente unas pocas:
-
Limpieza
descuidada de referencias. Puede ocurrir
una fuga de memoria cuando un objeto primario crea
otro objeto y, entonces, varios objetos adicionales
crean referencias a este último. Si no se “limpian” estas
referencias cuando se libera el objeto primario,
entonces se tendrá una fuga de memoria.
-
Diferentes
ciclos de vida. Cuando un objeto con un
ciclo de vida largo gestiona otro con un ciclo
de vida corto, hay potencial para una fuga de memoria.
-
Mal
manejo de excepciones. Algunas veces,
las excepciones pueden evitar el código
de limpieza. Por esta razón, debería
colocar este código en los bloques finally.
La
mayoría de fugas de memoria en Java están comprendidas
en una de las tres categorías anteriores. Estudiemos algunos
casos especiales de fugas:
-
La
colección con fuga. Este tipo de
fuga de memoria sucede cuando se coloca un objeto
en una colección y nunca se elimina de la
misma. Con una caché, esto sucede cuando
nunca se eliminan datos viejos de la misma.
-
El
patrón de diseño “Publish-Subscribe” permite
que un objeto que declara un evento publique una
interfaz y los suscriptores reciban notificación
cuando el evento ocurre. Cada vez que un objeto
se suscribe a un evento, es colocado en una colección.
Si desea que el recolector de basura libere el
objeto suscriptor, deberá cancelar la suscripción
cuando haya acabado con ella.
-
Singleton. Este
tipo de fuga de memoria ocurre cuando se tiene un
singleton con un ciclo de vida largo que referencia
un objeto con un ciclo de vida corto. Si no se pone
la referencia a null, entonces el recolector
de basura nunca liberará el objeto de vida
corta.
-
La
función costosa que apenas se usa. Muchas
aplicaciones tienen funciones, como Imprimir, que
pueden ser extremadamente costosas pero que son
usadas en pocas ocasiones. Si se referencia estas
funciones desde objetos con ciclos de vida largos,
es mejor limpiar explícitamente las referencias
entre los usos.
-
El
estado de sesión. Cuando se maneja
estado de sesión, se está sujeto
a fugas de memoria, porque es difícil determinar
cuando finaliza la sesión con un determinado
usuario.
Estas
son las fugas típicas. Seguramente, usted encontrará otras.
Como las fugas de memoria son muy difíciles de encontrar,
vale la pena comprender los patrones asociados con las mismas y aplicar
a esas áreas un mayor escrutinio. Cuando se descubra una fuga,
se deber ía usar una plataforma que ayude a identificarla.
|