3.6 Recolector de basura - ajpaez/OCA GitHub Wiki

(OCA Objective 2.4)

Overview sobre la gestión de memoria y el recolector de basura

La gestión de la memoria es un elemento crucial en muchos tipos de aplicaciones. Cuando se crean grandes cantidades de datos, tras su uso el espacio que ocupa en memoria debe ser liberado. Si esta operación se repite millones de veces y no se realiza el limpiado de los datos puedo producir perdida de memoria, memory leaks. El recolector de Java ( aka GC) de basura proporciona una solución automática para la gestión de la memoria. En la mayoría de los casos nos libera de tener que añadir cualquier lógica de gestión de memoria a nuestra aplicación. La desventaja del GC es que no se puede elegir cuando se lanza o cuando no.

Overview sobre el recolector de basura de Java

El GC es la frase usada para describir en Java la gestión de la memoria de forma automática. Cada vez que un programa se ejecuta utiliza memoria de varias maneras distintas. Se crea un stack, un heap, y en caso de Java un pools de constantes y áreas de memoria. el heap es la parte de memoria donde los objetos viven, y es la única parte de memoria donde se involucra el GC. Así que, el GC tiene la tarea de asegurarse que el heap tiene la cantidad de espacio libre como sea posible. Lo que se traduce para el examen en, borrar todos los objetos que ya no son alcanzables para la aplicaron Java en ejecución. Cuando se lanza el GC, su fin es encontrar y eliminar los objetos que no estén accesibles o estén descartados y borrarlos de la memoria.

¿Cuándo se ejecuta el recolector de basura?

El GC está bajo el control de la JVM, esta decide cuando lanzarlo. Desde una aplicación Java se le puede pedir a la JVM que lance el GC pero no existen garantías de que sea lanzado. Normalmente la JVM lanza el GC cuando detecta que se está agotando la memoria. La experiencia nos dice, que cuando se solicita a la JVM lanzar el GC nuestra solicitud sea atendida a corto plazo, pero no hay garantías de ello.

¿Cómo trabaja el recolector de basura?

Simplemente no se puede estar seguro. En pocas palabras, todos los programas Java tiene de uno a muchos hilos. Cada hilo tiene su propia stack pequeña. Normalmente siempre existe un hilo (main) ejecutándose al fondo del stack. Sin embargo, puede coexistir con este muchos mas hilos en la JVM. Podemos decir que un objeto esta disponible para el GC cuando ninguno hilo vivo puede acceder a dicho objeto. ¿Puede una aplicación Java quedarse sin memoria? Si, el GC recolecta los objetos que ya no se están usando, sin embargo, si existen demasiados objetos vivos en memoria el sistema puede quedarse sin memoria.

Escribir código que explícitamente cree objetos disponibles para el recolector de basura

A continuación veremos como hacer que los objetos queden aptos para el GC.

Referencias a Null

La primera forma de eliminar la referencia de un objeto es establecer su variable a null.

1. public class GarbageTruck {
2.   public static void main(String [] args) {
3.     StringBuffer sb = new StringBuffer("hello");
4.     System.out.println(sb);
5.     // The StringBuffer object is not eligible for collection
6.     sb = null;
7.     // Now the StringBuffer object is eligible for collection
8.   }
9. }

El objeto StringBuffer con el valor hello es asignado a la variable de referencia sb. Para hacer que el objeto sea apto para el GC basta con setear sb a null.

Reasignación de variables de referencia

También podemos separar a una variable de referencia de un objeto reasignando la variable a otro objeto. Como ocurre en el siguiente ejemplo:

class GarbageTruck {
  public static void main(String [] args) {
    StringBuffer s1 = new StringBuffer("hello");
    StringBuffer s2 = new StringBuffer("goodbye");
    System.out.println(s1);
    // At this point the StringBuffer "hello" is not eligible
    s1 = s2; // Redirects s1 to refer to the "goodbye" object
    // Now the StringBuffer "hello" is eligible for collection
  }
}

Podemos ver como asignamos el objeto s1 a s2 y por tanto el objeto que representa hello queda disponible para el GC. Los objetos que se crean dentro de un método, también están disponibles para el GC cuando dicho método acaba su ejecución. Tan solo existe una excepción, si se devuelve un objeto creado dentro del método y se asigna una variable de referencia fuera de dicho método, este objeto no estará disponible para el GC.

Aislamiento (isolation) de referencias

Esta es otra forma en la que objetos pasan a a estar disponibles para el GC, incluso si todavía tienen referencias validas. Un ejemplo simple es una clase que contiene variables de instancia que están referenciadas a otras variables de instancia de la misma clase. Ahora imagina que dos de dichas instancias existen y se refieren la una a la otra. Si se eliminas todas las demás referencias a estos dos objetos, a continuación, a pesar de cada objeto tiene todavía una referencia valida, no habrá ninguna manera de acceder a cualquier de los objetos. Por ejemplo:

public class Island {
  Island i;
  public static void main(String [] args) {
    Island i2 = new Island();
    Island i3 = new Island();
    Island i4 = new Island();
    i2.i = i3;   // i2 refers to i3
    i3.i = i4;   // i3 refers to i4
    i4.i = i2;   // i4 refers to i2
    i2 = null;
    i3 = null;
    i4 = null;
    // do complicated, memory intensive stuff
  }
}

Cuando finalice el código anterior, existirán 3 objetos Isalnd (i2, i3 e i4) que tiene variables de instancia que se refieren a otros objetos, pero que sus link al exterior (i2, i3 e i4) han sido setados a null. Estos tres objetos pasaran a estar disponibles para el GC.

enter image description here

Forzando el recolector de basura (OCP 5)

Tal y como se ha indicado en apartados anteriores el GC no puede ser forzado. Sin embargo, Java proporciona algunos métodos que permiten solicitar a la JVM llamar al GC. A partir del examen de Java 6, el tema de utilización de System.gc() ha sido eliminado. El GC ha evolucionado a un estado tan avanzado que se recomienda no invocarlo nunca.

##Limpiando antes que el GC - el método finalize()

Java proporciona un mecanismo que permite lanzar cualquier código antes de que un objeto sea eliminado por el GC. Dicho código se encuentra en el método finalize() que todas las clases heredan de la clase Object. El problema es que nunca se puede contar con que el GC elimine nuestro objeto. Por tanto, cualquier código que se escriba en el método sobreescrito finalize(), no está garantizado que sea ejecutado. Debido a que el método finalize() debería ejecutarse, pero no podemos contar con ello, no se debería de usar dicho método para escribir en el código esencial. De hecho, se recomienda que en general no se sobrescriba el método.

NOTAS para finalize()

Hay un par de conceptos relativos a finalize() que necesita recordar:

  • Para cualquier objeto dado, finalize() será llamado una sola vez (como máximo) por el recolector de basura.
  • Llamar a finalize() en realidad puede suponer un ahorro en la eliminación de un objeto.