style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">

El garbage collector y los memory leaks

Antecedente

Una de los aportes más relevantes de Java es la inclusión del recolector de basura ( garbage collector) tanto que hoy en día es un estándar y ni se menciona como una característica del lenguaje. Java no lo inventó, pero si aportó mucho para su adopción.

El GC corre automáticamente en la JVM cada cierto tiempo. No hay forma de proveer cuando lo hará (o si lo hará) ni de forzar a que corra (o que no lo haga). Existen varias estrategias de implementación y es todo un tema que sigue en desarrollo e investigación. Lo mejor de todo es que como usuarios de la plataforma tenemos muy poco que hacer para utilizarlo.

Referencias

La regla para que un objeto pueda ser recolectado es sencilla, no debe de haber referencias al objeto.

....
Empleado a = new Empleado("God"); // a es la 1ra referencia
Empleado b = a; // b es la segunda referencia al mismo objeto "God"
...

En el ejemplo anterior se creó un objeto de tipo Empleado y hay dos referencias a él (a y b). Este objeto no será recolectado por el gc y su memoria seguirá en uso.

Cuando ya no hay referencias al objeto:

a = new Empleado("inez"); // "a" ahora apunta a otro objeto solo queda una: b
b = null; // b ahora no apunta a nada, ya no quedan referencias al objeto "God"

Entonces el espacio de memoria de nuestro primer empleado puede ser reclamado por el gc y liberado.

Cuando se trata de variables creadas en un método ( ya sea en el cuerpo o en los parámetros ) las referencias se limpian al terminar la ejecución del método.

Ejemplo

Un ejemplo muy ilustrador del uso de memoria, del garbage collector y los memory leaks es la siguiente implementación de una pila.

(nota yo no inventé este ejemplo, pero ya olvidé donde lo vi por primera vez)

class Stack {

    private int index = -1;
    private Object [] data = new Object[10]; // un espacio para 10 elementos

    /** Agrega el elemento a la pila */
    public void push( Object o ) {
        checkUpperLimit();
        data[++index] = o;
    }
   
    /** Devuelve el elemento que está en el tope de la pila */
    public Object peek() {
        datos[index];
    }
     /** Devuelve el elemento del tope de la pila */
    public Object pop() {
        checkLowerLimit();
        data[index--];
    }

    // metodos auxiliares no relevantes en este momento....
    private void checkUpperLimit() { checkLimits(index+1);}
    private void checkLowerLimit() { checkLimits(index-1);}
    private void checkLimits( int futureValueOfIndex ) {
       if ( futureValueOfIndex >= data.lenght ) { throw new RuntimeException("StackOverflow :-o "); }
       if ( futureValueOfIndex <  -1          ) { throw new RuntimeException("StackUnderflow :-S"); }
    }
   
 }

Pueden identificar la fuga de memoria? Nope?

Una pista? Esta en el método pop().

La implementación es sencilla. Un arreglo y un indice para saber en donde está el tope.

- Con push() Se incrementa el indice y se pone el elemento ahí.
- Con peek() Se devuelve el elemento al que esté señalando el indice
- Con pop( ) Se devuelve el elemento señalado y después de decrementa el indice.

Lo malo es que pop solamente decrementa el apuntador, pero lo que se haya almacenado ahí, sigue ahí. Si se pone un objeto y luego este objeto se elimina, y el buen programador se encarga de eliminar todas las referencias, el gc no podrá hacer su trabajo por que aún existe una referencia en el arreglo. Solamente se perderá hasta que otro objeto ocupe ese espacio, pero mientras tanto el objeto ahí queda inaccesible y no reclamable.

....
public void produce() {
    Request request = Request.createRequest();
    stack.push( request );  // 2 referencias al objeto request...
}
public void consume() {
    RequestExecutor executor  = ...
    executor.handle( stack.pop() );  // deja un request en la  memoria interna del stack que no puede ser reclamable
}
...

Para arreglarlo hay que eliminar la referencia y listo.

...
public void pop() {
        Object value = peek();
        checkLowerLimit();
        data[index--] = null; // eliminamos la referencia y decrementamos el indice
        return value;
}

Chan chaaan. Se crea una referencia "value" pero esta se pierde al terminar el método. La referencia que estaba dentro del arreglo se pierde también al apuntarlo a otro lado ( null en este caso ) y no más memory leaks.

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de paranoid_android

Muy buena aportación

El tema de Memory Leaks, Garbage Colector y memoria de la máquina virtual genera muchas historias de terror, normalmente no hay mucha información en internet y estos temas van muy de la mano con errores de Infraestructura y de despliegue.

Alguna vez tuve un precioso error en un WAS “Resource Starvation” asociado a una aplicación que consumía todos los recursos de un servidor virtualizado.

Imagen de Sr. Negativo

Fugas de memoria

Si es feo cuando ocurre la famosa:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at ...

Y no sabes cómo resolverlo :(

"Nooo !! ... en mi casa si funcionaba mi programa"

Creemos que Java se encargará mágicamente de resolver todos nuestros problemas.

Sencilla explicación

Gracias por la explicación que das. Muy fácil de entender. ¿Qué herramienta para detectar memory leaks recomendarías?.

No hago esto mucho (

No hago esto mucho ( afortunadamente) pero he llegado a usar Visual VM lo mejor es que viene con el SDK

style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">