Está en la página 1de 20

Destrucción de Objetos en Java.

Cuando implementamos una clase, definimos sus atributos, sus


métodos, y por supuesto su(s) constructor(es). Y en ningún momento
implementamos algo que nos permita DESTRUIR el objeto cuando ya no
lo necesitemos. Esto se debe a que no tenemos una operación DISPOSE
que nos permita liberar la memoria ocupada por un objeto justamente
para evitarnos el tener que preocuparnos por la memoria usada por
nuestros programas. De este modo, al menos en objetos simples no
tiene sentido una operación que los destruya, es más, no tenemos
cómo implementarla.
¿Cómo liberamos entonces la memoria usada por
nuestros objetos? Pues en otros lenguajes tenemos
que tener especial cuidado de no dejar colgados a
nuestros objetos porque luego quedaban
inaccesibles y ocupando lugar de memoria. Esto
claramente es una mala práctica de programación.
Por ejemplo, si en Modula hacíamos:
p= CrearPersona(“Gaspar”,”Henaine”);

p= CrearPersona(“Doroteo”,”Arango”);
p= CrearPersona(“Gaspar”,”Henaine”);

p= CrearPersona(“Doroteo”,”Arango”);

Primero hemos creado un objeto Persona referenciado por


p y luego hemos creado otro objeto Persona referenciado
también por p. Esto hace que p deje de apuntar al primer
objeto creado y quede apuntando al segundo. De este
modo, el objeto con nombre Gaspar Henaine queda perdido
en memoria, inaccesible y ocupando espacio. Lo correcto
habría sido destruir el objeto Persona Gaspar Henaine antes
de crear el nuevo, o bien, referenciar dicho objeto con otra
variable.
En Java para liberar memoria justamente tenemos que dejarla
colgada. Sí, tenemos que dejar memoria colgada porque Java
será quién se encargue de liberarla luego, nosotros no tenemos
control sobre eso. De este modo, si yo hago en Java algo como
esto:
p= CrearPersona(“Gaspar”,”Henaine”);

p= null;

estoy dejando el objeto creado en la primera línea colgado en


memoria e inaccesible. Justamente eso es lo que Java necesita
saber para liberar esa memoria luego. O sea que, para liberar
memoria tengo que, a propósito, dejarla colgada. Así, todo lo
que estaba mal en otros lenguajes ahora en Java no nos da
problemas.
Por ejemplo:
p= new Persona(“Gaspar”,”Henaine”);
q= p;
p= null;
Allí hemos dejado la referencia p en null pero no
hemos afectado a q por tanto el objeto creado no
está inaccesible y por ende no está colgado en
memoria.

Entonces ¿cómo funciona realmente esto de la


gestión de memoria en Java?
Recolección de basura

Como estamos viendo, para liberar memoria tenemos que


dejarla colgada, no tenemos otro modo. Esto es porque en
Java existe un proceso llamado Garbage Collector (recolector
de basura) que cada tanto tiempo se ejecuta y busca en
memoria los objetos que están inaccesibles desde el
programa principal liberando la memoria ocupada por ellos,
es decir, este proceso se encarga justamente de buscar y
liberar todo lo que hemos dejado colgado y que por tanto se
considera basura. Entonces, el recolector de basura elimina
de memoria todo aquello que no está referenciado por nadie
o bien, que no es accesible desde el programa principal
¿Cuándo pasa el recolector de basura?

Este proceso es ejecutado por la máquina virtual de Java y


nosotros como programadores no tenemos ningún control
sobre él, por tanto se ejecuta esporádicamente o cuando el
sistema necesita memoria para otra cosa. Nunca se sabe
entonces cuando será ejecutado este proceso. Otro punto
importante es que la ejecución del recolector de basura no
implica necesariamente que se eliminen todos los objetos
que son considerados basura. Por tanto, si hemos dejado
cinco objetos colgados, cuando el recolector pase no tiene
por qué eliminar los cinco objetos. Nosotros tampoco
tenemos un control sobre eso.
Existe una instrucción que podemos utilizar para indicar a la
máquina virtual de Java que queremos que el recolector de
basura pase para limpiar la memoria la cual es:

System.gc();

Sin embargo enfatizaré específicamente la parte de que con


esto indicamos a la máquina virtual que QUEREMOS que el
recolector pase, pero no implica que la máquina virtual lo
ejecute y por tanto esa decisión dependerá de ella. De este
modo, por mucho énfasis que pongamos en querer liberar
memoria nunca sabremos efectivamente cuando será
ejecutado el recolector de basura.
Este proceso es muy inteligente, en el siguiente sentido:

Si tenemos por ejemplo una lista ligada y perdemos la


referencia al primer nodo estamos dejando entonces todo el
contenido de la lista colgado en memoria. En un caso así, a
pesar de que cada nodo referencia al siguiente y por ende
existen objetos que son referenciados por alguien, el
recolector de basura puede determinar que en realidad no
podemos llegar a ninguno de ellos desde el programa
principal.

Lo mismo sucede con una lista circular, un árbol binario, o


cualquier estructura de memoria dinámica.
Esto implica entonces que el recolector de basura puede
determinar cuando toda una enorme estructura llena de
punteros que referencian a objetos de todos lados son basura
o no. Si a es una referencia a un árbol binario de búsqueda
que contiene miles de nodos y yo hago a=null, el recolector
de basura podrá determinar que no es posible llegar a ningún
nodo del árbol desde el programa principal y por tanto lo
eliminará todo, ya no tenemos que programarlo nosotros.
La contraparte de esto es que la recolección de basura es
entonces un proceso muy pesado y que consume recursos,
por este motivo es la máquina virtual la que decide cuando es
necesario ejecutarlo, lo cual dependerá de la necesidad del
sistema operativo por usar la memoria ocupada, la carga del
procesador en el momento actual (si el procesador está muy
ocupado no conviene ejecutar el recolector), la necesidad de
nuestro programa por obtener nueva memoria, etc. Toda
esta complicación queda por parte de los programadores de
Java y por tanto nosotros solo la utilizamos.
Ejemplo:
Crearemos entonces cuatro objetos de tipo Persona y luego los
eliminaremos, es decir, los desreferenciaremos con el fin de que queden
como basura, inaccesibles por nosotros y por tanto nos
desentenderemos de ellos porque sabemos que Java los eliminará en
algún momento:
public class DatosPersonas {
public static void main(String[] args){
Persona p1= new Persona("Gaspar","Henaine");
Persona p2= new Persona("Gaspar","Henaine");
Persona p3= new Persona("Gaspar","Henaine");
Persona p4= new Persona("Gaspar","Henaine");
System.out.println(Persona.obtenerCantidadPersonas());
p1= null;
p2= null;
p3= null;
p4= null;
}
}
Hasta ahí todo bien, sin embargo si ustedes vuelven a
mostrar en pantalla el valor de la variable cantidadPersonas
verán que vuelve a salir el número 4. Entonces en realidad
esta variable lleva un conteo de los objetos instanciados
desde el inicio del programa sin tomar en cuenta los
eliminados. De este modo si a lo largo del tiempo de
ejecución de mi programa creo en total 1500 objetos, sea que
hayan convivido en memoria todos a la vez o no, la variable
marcará el valor 1500; más claramente, suma 1 cada vez que
creamos un objetos, jamás disminuye.
¿Cómo hacemos para restar 1 a la variable cuando se
destruya un objeto?

Deberíamos saber cuando el recolector de basura elimina


efectivamente a un objeto en memoria que es considerado
basura.

¿Cómo logramos esto?

Pues Java nos provee de una operación ya definida que se


ejecuta cuando un objeto va a ser eliminado, es decir, cuando
el recolector de basura va a reclamar la memoria ocupada
por un objeto que es basura este tiene la posibilidad de
ejecutar una última operación antes de ser borrado. Esta
operación se conoce con el nombre finalize.
La razón de la existencia de esta operación es darle al
programador la posibilidad de liberar algún posible recurso
que el objeto pueda estar usando antes de ser eliminado con
el fin de tener una buena gestión sobre ese recurso; un
ejemplo podría ser la conexión con una base de datos que
debería ser cerrada antes de eliminar al objeto que la
representa.

En este caso puntual nosotros usaremos la operación finalize


para restar 1 a la variable cantidadPersonas a fin de que
represente realmente la cantidad de objetos de la clase
Persona que existen en memoria en un momento dado.
La declaración de la operación finalize es:
public void finalize()

Entonces en nuestra clase Persona declaremos esta


operación dándole además un método como el que muestro
ahora:
@Override
public void finalize(){
Persona.cantidadPersonas--;
}

Vallamos ahora a la clase principal de DatosPersonas y


agreguemos estas dos líneas a lo que ya teníamos:

System.gc();

System.out.println(“Objetos en memoria ”+Persona.obtenerCantidadPersonas());


¿Qué hicimos? Pues agregamos un llamado al recolector de
basura para intentar que el sistema lo ejecute.

Luego mostramos en pantalla cuantos objetos quedan


efectivamente en memoria luego del llamado. Ejecuten el
programa varias veces y verán que el resultado puede variar.
Eso dependerá de si realmente el recolector de basura fue
ejecutado y además, en caso afirmativo, dependerá de si
fueron eliminados todos los objetos basura.
Tengan en cuenta que la variable cantidadPersonas indicará
entonces la cantidad de objetos de tipo Persona que existan
en memoria en un momento dado, sea que nosotros
tengamos referencias a ellos o no. No es lo mismo que llevar
un registro de la cantidad de objetos Persona que nosotros
tenemos referenciados.
public class DatosPersonas {
public static void main(String[] args){
Persona p1= new Persona("Gaspar","Henaine");
Persona p2= new Persona("Gaspar","Henaine");
Persona p3= new Persona("Gaspar","Henaine");
Persona p4= new Persona("Gaspar","Henaine");
System.out.println(Persona.obtenerCantidadPersonas());
p1= null;
p2= null;
p3= null;
p4= null;
System.gc();
System.out.println(“Objetos en memoria ”+Persona.obtenerCantidadPersonas());
}
}

También podría gustarte