JavaRush /Blogue Java /Random-PT /Referência Fantasma em Java

Referência Fantasma em Java

Publicado no grupo Random-PT
Olá! Na lição de hoje falaremos detalhadamente sobre PhantomReferences em Java. Que tipo de links são esses, por que são chamados de “fantasmas” e como utilizá-los? Como você se lembra, existem 4 tipos de links em Java:
  1. StrongReference (referências regulares que criamos ao criar um objeto):

    Cat cat = new Cat()

    cat neste exemplo é uma referência forte.

  2. SoftReference (referência suave). Tivemos uma palestra sobre esses links.

  3. WeakReference (referência fraca). Houve uma palestra sobre eles também, aqui .

  4. PhantomReference (referência fantasma).

Os objetos dos três últimos tipos são digitados (por exemplo, SoftReference<Integer> , WeakReference<MyClass> ). Classes SoftReferencee herda de WeakReferenceclass . Os métodos mais importantes ao trabalhar com essas classes são: PhantomReferenceReference
  • get()- retorna o objeto ao qual este link se refere;

  • clear()— exclui uma referência a um objeto.

Você se lembra desses métodos nas palestras sobre SoftReferencee WeakReference. É importante lembrar que eles funcionam de maneira diferente com diferentes tipos de links. Hoje não consideraremos detalhadamente os três primeiros tipos, mas falaremos sobre links fantasmas. Também abordaremos outros tipos de links, mas apenas na parte em que falaremos sobre como os links fantasmas diferem deles. Ir! :) Vamos começar explicando por que precisamos de links fantasmas em primeiro lugar. Como você sabe, o coletor de lixo (Garbage Collector ou gc) é responsável por liberar memória de objetos Java desnecessários . O coletor remove o objeto em duas “passagens”. Na primeira passagem, ele apenas analisa os objetos e, se necessário, os marca como “desnecessários e a serem excluídos”. Se este objeto tiver um método substituído finalize(), ele será chamado. Ou não é chamado - dependendo da sua sorte. Você provavelmente se lembra que isso finalize()é algo inconstante :) Na segunda passagem do coletor, o objeto é deletado e a memória é liberada. Esse comportamento imprevisível do coletor de lixo cria vários problemas para nós. Não sabemos exatamente quando o coletor de lixo começará a funcionar. Não sabemos se o método será chamado finalize(). Além disso, durante a operação, finalize()um link forte para o objeto pode ser criado e então ele não será excluído. Em sistemas que exigem muita memória, isso pode facilmente levar a arquivos OutOfMemoryError. Tudo isso nos leva a usar links fantasmas . A questão é que isso muda o comportamento do coletor de lixo. Se houver apenas links fantasmas para o objeto, então ele terá:
  • o método é chamado finalize()(se for substituído);

  • se depois do trabalho finalize()nada mudou e o objeto ainda pode ser excluído, uma referência fantasma ao objeto é colocada em uma fila especial - ReferenceQueue.

A coisa mais importante a entender ao trabalhar com referências fantasmas é que um objeto não é removido da memória enquanto sua referência fantasma estiver nesta fila . Ele será excluído somente após o link fantasma clear(). Vejamos um exemplo. Primeiro, vamos criar uma classe de teste que armazenará alguns dados.
public class TestClass {
   private StringBuffer data;
   public TestClass() {
       this.data = new StringBuffer();
       for (long i = 0; i < 50000000; i++) {
           this.data.append('x');
       }
   }
   @Override
   protected void finalize() {
       System.out.println("У an object TestClass вызван метод finalize!!!");
   }
}
Nós deliberadamente “carregamos” objetos com dados ao criá-los (adicionando 50 milhões de caracteres “x” a cada objeto) para ocupar mais memória. Além disso, substituímos especificamente o método finalize()para verificar se funcionou. Em seguida, precisamos de uma classe que herdará de PhantomReference. Por que precisamos dessa classe? É simples. Dessa forma, podemos adicionar lógica adicional ao método clear()para ver se a referência fantasma foi realmente apagada (e, portanto, o objeto foi excluído).
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class MyPhantomReference<TestClass> extends PhantomReference<TestClass> {

   public MyPhantomReference(TestClass obj, ReferenceQueue<TestClass> queue) {

       super(obj, queue);

       Thread thread = new QueueReadingThread<TestClass>(queue);

       thread.start();
   }

   public void cleanup() {
       System.out.println("Очистка фантомной ссылки! Удаление an object из памяти!");
       clear();
   }
}
Em seguida, precisaremos de um thread separado que aguardará até que o coletor de lixo faça seu trabalho, e ReferenceQueuelinks fantasmas aparecerão em nossa fila. Assim que esse link entrar na fila, seu método será chamado cleanup():
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;

public class QueueReadingThread<TestClass> extends Thread {

   private ReferenceQueue<TestClass> referenceQueue;

   public QueueReadingThread(ReferenceQueue<TestClass> referenceQueue) {
       this.referenceQueue = referenceQueue;
   }

   @Override
   public void run() {

       System.out.println("Поток, отслеживающий очередь, стартовал!");
       Reference ref = null;

       //ждем, пока в очереди появятся ссылки
       while ((ref = referenceQueue.poll()) == null) {

           try {
               Thread.sleep(50);
           }

           catch (InterruptedException e) {
               throw new RuntimeException("Поток " + getName() + " был прерван!");
           }
       }

       //How только в очереди появилась фантомная link - очистить ее
       ((MyPhantomReference) ref).cleanup();
   }
}
E finalmente, precisamos de um método main(): vamos colocá-lo em uma classe separada Main. Nele criaremos um objeto TestClass, uma referência fantasma para ele e uma fila para referências fantasmas. Depois disso vamos ligar para o coletor de lixo e ver o que acontece :)
import java.lang.ref.*;

public class Main {

   public static void main(String[] args) throws InterruptedException {
       Thread.sleep(10000);

       ReferenceQueue<TestClass> queue = new ReferenceQueue<>();
       Reference ref = new MyPhantomReference<>(new TestClass(), queue);

       System.out.println("ref = " + ref);

       Thread.sleep(5000);

       System.out.println("Вызывается сборка мусора!");

       System.gc();
       Thread.sleep(300);

       System.out.println("ref = " + ref);

       Thread.sleep(5000);

       System.out.println("Вызывается сборка мусора!");

       System.gc();
   }
}
Saída do console: ref = MyPhantomReference@4554617c O thread que monitora a fila foi iniciado! A coleta de lixo é chamada! O método finalize foi chamado no objeto TestClass!!! ref = MyPhantomReference@4554617c A coleta de lixo está sendo chamada! Limpando o link fantasma! Removendo um objeto da memória! O que vemos aqui? Tudo aconteceu como planejamos! Nossa classe de objeto teve um método substituído finalize()e foi chamado enquanto o coletor estava em execução. Em seguida, o link fantasma foi colocado na fila ReferenceQueue. Lá ela tinha um método chamado clear()(do qual fizemos cleanup()para adicionar saída ao console). Como resultado, o objeto foi removido da memória. Agora você vê exatamente como funciona :) Claro, você não precisa memorizar toda a teoria associada aos links fantasmas. Mas será bom se você lembrar pelo menos os pontos principais. Em primeiro lugar , estes são os elos mais fracos de todos. Eles entram em jogo somente quando não há outras referências ao objeto. A lista de links que fornecemos acima está em ordem decrescente de força: StrongReference-> SoftReference-> WeakReference-> PhantomReference O link fantasma entrará em batalha somente quando não houver links fortes, suaves ou fracos para nosso objeto :) Em segundo lugar , o método get()para link fantasma sempre retorna null. Aqui está um exemplo simples onde criamos três tipos diferentes de links para três tipos diferentes de carros:
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;

public class Main {

   public static void main(String[] args) {

       Sedan sedan = new Sedan();
       HybridAuto hybrid = new HybridAuto();
       F1Car f1car = new F1Car();

       SoftReference<Sedan> softReference = new SoftReference<>(sedan);
       System.out.println(softReference.get());

       WeakReference<HybridAuto> weakReference = new WeakReference<>(hybrid);
       System.out.println(weakReference.get());

       ReferenceQueue<F1Car> referenceQueue = new ReferenceQueue<>();

       PhantomReference<F1Car> phantomReference = new PhantomReference<>(f1car, referenceQueue);
       System.out.println(phantomReference.get());

   }
}
Saída do console: Sedan@4554617c HybridAuto@74a14482 null O método get()retornou objetos bastante normais para o link flexível e o link fraco, mas retornou nullpara o fantasma. Em terceiro lugar , a principal área de uso de referências fantasmas são os procedimentos complexos para remover objetos da memória. Isso é tudo! :) Isso conclui nossa lição de hoje. Mas a teoria por si só não levará você muito longe, então é hora de voltar a resolver os problemas! :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION