¡Hola!

Creo que no te sorprenderá si te digo que tu ordenador tiene una cantidad limitada de memoria :) Incluso un disco duro, generalmente muchas veces más grande que el almacenamiento de RAM, puede llenarse con tus juegos favoritos, programas de televisión y más. Para evitar que esto suceda, es necesario controlar el estado actual de la memoria y eliminar archivos innecesarios de tu ordenador. ¿Qué tiene que ver la programación en Java con todo esto? ¡Todo! Después de todo, cuando la máquina Java crea cualquier objeto, asigna memoria para ese objeto.

En un programa grande real, se crean decenas y cientos de miles de objetos, y cada uno de ellos tiene su propia parte de memoria asignada. Pero, ¿cuánto tiempo crees que existen todos estos objetos? ¿"Viven" durante todo el tiempo que se ejecuta nuestro programa? Por supuesto que no. Incluso con todas las ventajas de los objetos en Java, no son inmortales :) Los objetos tienen su propio ciclo de vida. Hoy haremos una pequeña pausa en la escritura de código y veremos este proceso :) Además, es muy importante para tu comprensión de cómo funciona un programa y cómo se administran los recursos. Entonces, ¿cuándo comienza la vida de un objeto? Como una persona, desde su nacimiento, es decir, su creación.


Cat cat = new Cat(); // Here the lifecycle of our Cat object begins!

En primer lugar, la Máquina Virtual de Java asigna la cantidad requerida de memoria para crear el objeto. Luego, crea una referencia a esa memoria. En nuestro caso, esa referencia se llama cat, para que podamos realizar un seguimiento de ella. Luego, se inicializan todas sus variables, se llama al constructor y, ¡ta-dá! ¡Nuestro recién creado objeto está viviendo su propia vida! :)

La duración de vida de los objetos varía, por lo que no podemos proporcionar números exactos aquí. En cualquier caso, vive por algún tiempo dentro del programa y realiza sus funciones. Para ser precisos, un objeto "vive" siempre y cuando haya referencias a él. Tan pronto como no haya más referencias, el objeto "muere". Ejemplo:


public class Car {
  
   String model;

   public Car(String model) {
       this.model = model;
   }

   public static void main(String[] args) {
       Car lamborghini  = new Car("Lamborghini Diablo");
       lamborghini = null;

   }

}

El objeto de carro Lamborghini Diablo deja de existir en la segunda línea del método main(). Solo había una referencia a él y luego esa referencia se estableció en null. Dado que no hay más referencias al Lamborghini Diablo, la memoria asignada se convierte en "basura". No es necesario establecer una referencia en nulo para que esto suceda:


public class Car {

   String model;

   public Car(String model) {
       this.model = model;
   }

   public static void main(String[] args) {
       Car lamborghini  = new Car("Lamborghini Diablo");

       Car lamborghiniGallardo = new Car("Lamborghini Gallardo");
       lamborghini = lamborghiniGallardo;
   }

}

Aquí creamos un segundo objeto y luego asignamos este nuevo objeto a la referencia lamborghini. Ahora el objeto Lamborghini Gallardo tiene dos referencias, pero el objeto Lamborghini Diablo no tiene ninguna. Eso significa que el objeto Diablo ahora es basura. Es entonces cuando entra en juego el mecanismo integrado de Java llamado recolector de basura (GC).

El recolector de basura es un mecanismo interno de Java responsable de liberar memoria, es decir, de eliminar objetos innecesarios de la memoria. Hay una buena razón por la que elegimos una imagen de un robot aspirador aquí. Después de todo, el recolector de basura funciona de manera muy similar: en segundo plano, "viaja" por su programa, recolectando basura con prácticamente ningún esfuerzo por su parte. Su trabajo es eliminar objetos que ya no se utilizan en el programa.

Al hacer esto, se libera memoria en la computadora para otros objetos. ¿Recuerda que al comienzo de la lección dijimos que en la vida ordinaria debe controlar el estado de su computadora y eliminar archivos innecesarios? Bueno, en el caso de los objetos Java, el recolector de basura lo hace por usted. El recolector de basura se ejecuta repetidamente a medida que se ejecuta su programa: no tiene que llamarlo explícitamente ni darle comandos, aunque esto es técnicamente posible. Más adelante hablaremos más de ello y analizaremos su trabajo con mayor detalle.

Cuando el recolector de basura alcanza un objeto, justo antes de destruirlo, llama a un método especial: finalize(), en el objeto. Este método puede liberar otros recursos utilizados por el objeto. El método finalize() forma parte de la clase Object. Eso significa que, además de los métodos equals(), hashCode() y toString() que ha visto anteriormente, cada objeto tiene este método. Se diferencia de otros métodos en que es — cómo debería decir esto — muy caprichoso.

En particular, no siempre se llama antes de la destrucción de un objeto. La programación es un esfuerzo preciso. El programador le dice a la computadora que haga algo y la computadora lo hace. Supongo que ya está acostumbrado a este comportamiento, por lo que al principio puede ser difícil para usted aceptar esta siguiente idea: "Antes de la destrucción de los objetos, se llama al método finalize() de la clase Object. O tal vez no se llame. ¡Todo depende de tu suerte!"

Aún así, es verdad. La propia máquina Java determina si llamar o no al método finalize() caso por caso. Por ejemplo, intentemos ejecutar el siguiente código como experimento:


public class Cat {

   private String name;

   public Cat(String name) {
       this.name = name;
   }

   public Cat() {
   }

   public static void main(String[] args) throws Throwable {
       for (int i = 0 ; i < 1000000; i++) {
           Cat cat = new Cat();
           cat = null; // This is when the first object becomes available to the garbage collector
       }
   }

   @Override
   protected void finalize() throws Throwable {
       System.out.println("Cat object destroyed!");
   }
}

En este ejemplo, creamos un objeto de tipo Cat y en la siguiente línea de código establecemos su única referencia como null. Y lo hacemos un millón de veces. Explotícitamente anulamos el método finalize() para que imprima una cadena en la consola un millón de veces (una vez por cada vez que se destruye un objeto Cat). Pero no. Para ser precisos, solo se ejecutó 37,346 veces en mi computadora. Es decir, solo una vez en 27 ocasiones la máquina Java instalada en mi computadora decidió llamar al método finalize().

En otros casos, la recolección de basura se produce sin él. Prueba a ejecutar este código por ti mismo: lo más probable es que obtengas un resultado diferente. Como puedes ver, finalize() apenas puede ser considerado un compañero confiable. Por lo tanto, un pequeño consejo para el futuro: no confíes en el método finalize() para liberar recursos críticos. Quizás la JVM lo llame, o tal vez no. ¿Quién sabe?

Si mientras el objeto está vivo, tiene algunos recursos que son súper importantes para el rendimiento, por ejemplo, una conexión de base de datos abierta, es mejor crear un método especial en tu clase para liberarlos y luego llamarlo explícitamente cuando el objeto ya no sea necesario. De esa manera, sabrás con certeza que el rendimiento de tu programa no se verá afectado. Desde el principio, dijimos que trabajar con memoria y eliminar la basura es muy importante, y esto es cierto. Manejar incorrectamente los recursos y malinterpretar cómo se eliminan los objetos innecesarios puede provocar fugas de memoria. Este es uno de los errores de programación más conocidos.

Si los programadores escriben su código de manera incorrecta, se puede asignar nueva memoria para los objetos recién creados cada vez, mientras que los objetos viejos e innecesarios pueden no estar disponibles para ser eliminados por el recolector de basura. Dado que hicimos una analogía con una aspiradora robot, imagina lo que pasaría si, antes de comenzar la aspiradora, dispersas calcetines por la casa, rompes un florero de cristal y dejas bloques de construcción de Lego por todo el piso. La aspiradora intentará hacer su trabajo, por supuesto, pero en algún momento se atascará.

Para permitir que la aspiradora robot funcione correctamente, debes mantener el piso en buenas condiciones y eliminar todo lo que la aspiradora no pueda manejar. El mismo principio se aplica al recolector de basura de Java. Si hay muchos objetos que no se pueden limpiar en un programa (como un calcetín o un bloque de construcción de Lego para nuestra aspiradora robot), en algún momento te quedarás sin memoria. Y no solo puede ser tu programa el que se congele: cualquier otro programa en ejecución en la computadora puede verse afectado. También puede que no tengan suficiente memoria.

No necesitas memorizar esto. Solo necesitas entender el principio detrás de cómo funciona.