Oi!

Acho que você não ficará muito surpreso se eu disser que seu computador tem uma quantidade limitada de memória :) Mesmo um disco rígido - geralmente muitas vezes maior que o armazenamento RAM - pode ser compactado com seus jogos favoritos, programas de TV, e mais. Para evitar que isso aconteça, você precisa monitorar o estado atual da memória e excluir arquivos desnecessários do seu computador. O que a programação Java tem a ver com tudo isso? Tudo! Afinal, quando a máquina Java cria qualquer objeto, ela aloca memória para esse objeto.

Em um programa realmente grande, dezenas e centenas de milhares de objetos são criados, e cada um deles tem seu próprio pedaço de memória alocado para ele. Mas há quanto tempo você acha que todos esses objetos existem? Eles "vivem" o tempo todo em que nosso programa está sendo executado? Claro que não. Mesmo com todas as vantagens dos objetos Java, eles não são imortais :) Os objetos têm seu próprio ciclo de vida. Hoje faremos uma pequena pausa na escrita do código e veremos esse processo :) Além disso, é muito importante para você entender como um programa funciona e como os recursos são gerenciados. Então, quando começa a vida de um objeto? Como uma pessoa - desde o seu nascimento, isto é, a criação.


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

Primeiro, a Java Virtual Machine aloca a quantidade necessária de memória para criar o objeto. Em seguida, ele cria uma referência a essa memória. No nosso caso, essa referência é chamada cat, para que possamos acompanhá-la. Em seguida, todas as suas variáveis ​​são inicializadas, o construtor é chamado e — ta-da! — nosso objeto recém-criado está vivendo sua própria vida :)

A vida útil dos objetos varia, então não podemos fornecer números exatos aqui. De qualquer forma, ele vive algum tempo dentro do programa e executa suas funções. Para ser preciso, um objeto está "vivo" enquanto houver referências a ele. Assim que não houver mais referências, o objeto "morre". Exemplo:


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;

   }

}

O objeto carro Lamborghini Diablo deixa de estar ativo na segunda linha do main()método. Havia apenas uma referência a ela e, em seguida, essa referência foi definida como igual a null. Como não há referências restantes ao Lamborghini Diablo, a memória alocada se torna "lixo". Uma referência não precisa ser definida como nula para que isso aconteça:


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;
   }

}

Aqui criamos um segundo objeto e atribuímos esse novo objeto à lamborghinireferência. Agora o Lamborghini Gallardoobjeto tem duas referências, mas o Lamborghini Diabloobjeto não tem nenhuma. Isso significa que o Diabloobjeto agora é lixo. É quando o mecanismo interno do Java chamado coletor de lixo (GC) entra em ação.

O coletor de lixo é um mecanismo interno do Java responsável por liberar memória, ou seja, remover objetos desnecessários da memória. Há uma boa razão pela qual escolhemos a foto de um aspirador de pó robô aqui. Afinal, o coletor de lixo funciona praticamente da mesma forma: em segundo plano, ele "viaja" pelo seu programa, coletando lixo praticamente sem esforço de sua parte. Sua função é remover objetos que não são mais usados ​​no programa.

Isso libera memória no computador para outros objetos. Você se lembra que no início da aula dissemos que na vida cotidiana você deve monitorar o estado do seu computador e excluir arquivos desnecessários? Bem, no caso de objetos Java, o coletor de lixo faz isso para você. O coletor de lixo é executado repetidamente enquanto seu programa é executado: você não precisa chamá-lo explicitamente ou dar comandos a ele, embora isso seja tecnicamente possível. Mais adiante falaremos mais sobre ela e analisaremos seu funcionamento com mais detalhes.

Quando o coletor de lixo atinge um objeto, pouco antes de destruí-lo, ele chama um método especial — finalize()— no objeto. Este método pode liberar outros recursos utilizados pelo objeto. O finalize()método faz parte da Objectclasse. equals()Isso significa que , além dos métodos , hashCode()e toString()que você conheceu anteriormente, todo objeto possui esse método. Difere de outros métodos porque é - como devo dizer - muito caprichoso.

Em particular, nem sempre é chamado antes da destruição de um objeto. A programação é um esforço preciso. O programador diz ao computador para fazer algo, e o computador faz. Suponho que você já esteja acostumado com esse comportamento, então a princípio pode ser difícil para você aceitar a seguinte ideia: "Antes da destruição dos objetos, o finalize()método da Objectclasse é chamado. Ou talvez não seja chamado. Tudo depende de sua sorte!"

No entanto, é verdade. A própria máquina Java determina se deve ou não chamar o finalize()método caso a caso. Por exemplo, vamos tentar executar o seguinte código como um 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!");
   }
}

Criamos um Catobjeto e, na próxima linha de código, definimos sua única referência igual a null. E fazemos isso um milhão de vezes. Sobrescrevemos explicitamente o finalize()método para que ele imprima uma string no console um milhão de vezes (uma vez para cada vez que destrói um Catobjeto). Mas não! Para ser preciso, ele rodou apenas 37.346 vezes no meu computador! Ou seja, apenas uma vez em 27 vezes a máquina Java instalada em minha máquina decidiu chamar o finalize()método.

Em outros casos, a coleta de lixo aconteceu sem ela. Tente executar este código você mesmo: provavelmente, você obterá um resultado diferente. Como você pode ver, finalize()dificilmente pode ser chamado de parceiro confiável :) Portanto, um pequeno conselho para o futuro: não confie no finalize()método para liberar recursos críticos. Talvez a JVM o chame, ou talvez não. Quem sabe?

Se enquanto seu objeto está vivo ele guarda alguns recursos superimportantes para performance, por exemplo, uma conexão de banco de dados aberta, é melhor criar um método especial em sua classe para liberá-los e depois chamá-lo explicitamente quando o objeto não estiver mais necessário. Dessa forma, você terá certeza de que o desempenho do seu programa não será prejudicado. Desde o início, dissemos que trabalhar com memória e remover o lixo é muito importante, e isso é verdade. O manuseio inadequado de recursos e a má compreensão de como os objetos desnecessários são limpos podem levar a vazamentos de memória. Este é um dos erros de programação mais conhecidos.

Se os programadores escreverem seu código incorretamente, nova memória pode ser alocada para objetos recém-criados a cada vez, enquanto objetos antigos e desnecessários podem não estar disponíveis para remoção pelo coletor de lixo. Já que fizemos uma analogia com um robô aspirador de pó, imagine o que aconteceria se, antes de ligar o robô, você espalhasse meias pela casa, quebrasse um vaso de vidro e deixasse blocos de Lego espalhados pelo chão. O robô tentará fazer seu trabalho, é claro, mas em algum momento ficará preso.

Para permitir o bom funcionamento do aspirador robot, é necessário manter o chão em boas condições e remover tudo o que o robot não consegue manusear. O mesmo princípio se aplica ao coletor de lixo do Java. Se houver muitos objetos deixados em um programa que não podem ser limpos (como uma meia ou um bloco de construção de Lego para nosso aspirador de pó robô), em algum momento você ficará sem memória. E pode não ser apenas o seu programa que irá congelar - todos os outros programas em execução no computador podem ser afetados. Eles também podem não ter memória suficiente.

Você não precisa memorizar isso. Você só precisa entender o princípio por trás de como funciona.