Coletor de Lixo


Tornando Objetos Visíveis para a Coleta de Lixo Explicitamente: Referência Nula

Um objeto se torna elegível para a Coleta de Lixo quando não há mais referência a ele. A primeira maneira de remover a referência de um objeto é setando-o como null.

public class Buffer {
      public static void main(String[] args) {
            StringBuffer sb = new StringBuffer("s");
            /* quando uma variável deixa de apontar
             * para um objeto recebendo o valor null,
             * esse objeto passará a ficar visível para
             * o coletor de lixo*/
            sb = null;
      }
}

Tornando Objetos Visíveis para a Coleta de Lixo Explicitamente: Reatribuindo uma Variável de Referência

É possível dissociar uma variável de referência de um objeto reatribuindo a variável de referência para outro objeto; ou seja, uma variável em determinado momento aponta para o objeto1, ao ser reatribuída para o objeto2, o objeto1 ficará disponível para o coletor, pois não possui mais uma variável de referência apontando para ele.
Variáveis locais são criadas quando o método é invocado, da mesma forma, quando o compilador chega ao final da execução do método, essas variáveis ficam disponíveis para a Coleta de Lixo. Entretanto, existe uma exceção: se um objeto é retornado do método, sua referência será reatribuída à variável de referência que está esperando o retorno, ou seja, essa referência da variável local não ficará elegível.

import java.util.Date;

public class Coletor {

      public static void main(String[] args) {
            StringBuffer s1 = new StringBuffer("s1");
            StringBuffer s2 = new StringBuffer("s2");
            /* quando uma variável deixa de apontar
             * para um objeto e passar a apontar para
             * outro, o primeiro não terá mais nenhuma
             * variável apontando para ele e passará a
             * ficar visível para o coletor; nesse caso
             * o s1. */
            s1 = s2;
           
            Date data = new Date();
            setDate(data);
           
            Date data2 = new Date();
            /* recebe a referência da variável local
             * que é retornada do método */
            data2 = getDate();
      }
     
      /* variáveis locais ficam visíveis para o
       * coletor quando o método termina a execução */
      static void setDate(Date dt) {
            System.out.println(dt);
      }
     
      /* quando o método retorna um objeto, na verdade
       * retorna a referência, o que faz com que a
       * variável local não fique visível para o
       * coletor, já que essa referência não mais
       * ficará perdida ao ser reatribuída a outra
       * variável */
      static Date getDate() {
            return new Date();
      }

}

Tornando Objetos Visíveis para a Coleta de Lixo Explicitamente: Isolando uma Referência

Numa classe onde existem objetos que são instância dessa mesma classe, se esses objetos referenciarem um ao outro e depois receberem atribuições null, eles estarão disponíveis para a Coleta de Lixo.

Forçando a Coleta de Lixo

Não existe uma maneira direta de forçar a coleta, mas existem métodos que permitem a JVM chamar a coleta de lixo. Porém isso não é garantido de imediato, mas o compilador irá tentar atender a requisição o mais rápido possível.
As rotinas que compõem o coletor de lixo são da classe Runtime, que é uma classe especial que contém um único objeto (a Singleton) para cada programa main.
O objeto Runtime oferece um mecanismo para comunicação direta com a máquina virtual. Para pegar a instância de Runtime é possível usar o método Runtime.getRuntime(), que retorna o Singleton.
O objeto singleton pode invocar o coletor através do método gc(). Alternativamente, é possível chamar o mesmo método da classe System (System,gc();).

import java.util.Date;

public class GC {
      public static void main(String[] args) {
            Runtime rt = Runtime.getRuntime();
            //um objeto na memória
            Date d = new Date();
            //passa a ficar visível para o coletor
            d = null;
            /* indica ao coletor q é necessário limpar
             * a memória do q n está sendo utilizado*/
            rt.gc();
           
            //outra forma de chamar o coletor
            System.gc();
      }
}

Limpando antes do Coletor de Lixo - O método finalize()

Java fornece um mecanismo para executar algum código antes do objeto ser excluído pelo coletor. Esse código está no método finalize(), que todas as classes herdam da classe Object.
Isso soa uma grande idéia, pois se o objeto abriu alguns recursos e é necessário fechá-los antes que o objeto seja destruído, essa é uma boa opção.
O problema é que não se sabe o momento que o coletor irá destruir os objetos, ou se até mesmo o fará, ou seja, não se pode garantir que o código que estiver dentro do finalize() será executado.
Regras: para qualquer objeto o método finalize() será chamado apenas uma vez pelo coletor; chamar o método finalize() pode resultar em salvar o objeto da destruição; se o objeto não for destruído, quando ficar elegível novamente para o coletor, este não executará mais o finalize().


Fonte: SCJP Sun Certified Programmer for Java 6 Study Guide

Sobrecarga com Autoboxing Ampliação e Var-arg


Quando uma classe possui métodos sobrecarregados o trabalho do compilador é decidir qual método chamar quando for invocado.
Por exemplo, passar um argumento byte para um método que recebe um int não tem problema, mas se existir uma sobrecarga com um argumento byte o compilador dará preferência a esse último porque não terá que fazer ampliação.

Sobrecarga com Boxing e Var-arg

No caso de ter dois métodos sobrecarregados, um com argumento long, por exemplo, e outro com argumento Integer, se for passado um parâmetro do tipo int o compilador escolherá o método com o argumento long, pois é menos custoso fazer cast de int pra long do que fazer autoboxing. Além disso, o compilador sempre optará pela forma mais antiga, e nesse caso o autoboxing é apenas a partir do Java 5.
No caso de ter dois métodos sobrecarregados, um com dois argumentos int e outro com um var-arg de byte, ao chamar o método passando duas variáveis byte, o método invocado será o com dois argumentos int, pois, apesar de requerer cast, o compilador irá escolher o modo mais antigo, já que var-arg é algo mais novo, deixando o código mais robusto.
Então, o cast (ampliação) de primitivos sempre terá preferência em relação ao autoboxing e var-arg, enquanto que o autoboxing sempre terá preferência em relação a var-arg, ou seja, o var-arg sempre será a última opção.


public class BoxAmpliacaoVararg {

      static void go(Integer x) {
            System.out.println("Integer");
      }

      static void go(long x) {
            System.out.println("long");
      }
     
      static void go(int x, int y) {
            System.out.println("int, int");
      }

      static void go(byte... x) {
            System.out.println("byte... ");
      }
     
      static void go(Byte x, Byte y) {
            System.out.println("Byte, Byte");
      }

      public static void main(String[] args) {
            /* é menos custoso fazer cast de int para
             * long do que fazer autoboxing de int
             * para Integer */
            int i = 5;
            go(i); // imprime long

            /* é menos custoso fazer cast de byte para
             * int do que utilizar var-arg de byte */
            byte b = 5;
            go(b, b); // imprime int, int
           
            /* o compilador dará mais pereferência
             * ao autoboxing de byte para Byte do que
             * utilizar o var-arg de byte */
            byte b2 = 5;
            go(b2, b2); // imprime Byte, Byte
      }

}


Sobrecarga combinando Ampliação e Boxing

No caso em que é requerida uma ampliação e auboxing juntos o compilador apresentará falha. Por exemplo, se houver um método com um parâmetro Long, e for passado um argumento byte, seria necessário primeiro fazer a ampliação de byte para long, para que depois o compilador pudesse fazer o autoboxing de long para Long. O compilador não fará isso automaticamente.


public class AmpliacaoBox {

      static void go(Long x) {
            System.out.println("Long");
      }

      public static void main(String[] args) {
            byte b = 5;
            /* erro - o compilador não aceita
             * fazer ampliação de byte para long
             * e ao mesmo tempo boxing de long
             * para Long */
            go(b);
           
            long l = b;
            go(l); //imprime Long
      }

}


Porém, se, nesse caso, o argumento for um Object, funcionará, pois o compilador irá primeiro fazer o autoboxing de byte para Byte, e o método poderá receber esse parâmetro, pois Byte IS-A Object.

public class BoxAmpliacao {

      static void go(Object o) {
            /* se o obj passado for Byte não
             * haverá problemas */
            Byte b2 = (Byte) o;
            System.out.println(b2);
      }

      public static void main(String[] args) {
            byte b = 5;
            go(b); //imprime 5
      }
     
}


Sobrecarga em Combinação com Var-arg

É possível combinar var-arg com ampliação ou autoboxing. Por exemplo, um método com um argumento var-arg do tipo long, pode ser invocado passando dois parâmetros int, pois o compilador irá ampliar para um long, e depois usará o var-arg. Um método com um argumento var-arg do tipo Integer também pode ser invocado passando dois parâmetros int, pois o compilador irá realizar o boxing de int para Integer, e depois usará o var-arg.


public class Sobrecarga_Vararg {

      static void wide_vararg(long... x) {
            System.out.println("long...");
      }

      static void box_vararg(Integer... x) {
            System.out.println("Integer...");
      }

      public static void main(String[] args) {
            int i = 5;
            /* o compilador irá fazer a ampliação
             * de int para long e depois utilizará
             * o vararg de long */
            wide_vararg(i, i); // imprime long
            /* o compilador irá fazer o autoboxing
             * de int para Integer e irá utilizar
             * o vararg de Integer */
            box_vararg(i, i); // imprime Integer
      }

}


Regras: a ampliação de primitivos usa o menor argumento de método possível; usado individualmente, boxing e var-args são compatíveis com overloading; não é permitido ampliar de uma classe wrapper para outra; não é permitido ampliar e depois fazer boxing (um int não pode se tornar um Long); é possível fazer boxing e ampliação (um int pode se tornar um Object, via Integer); é possível combinar var-args com ampliação e boxing.



Fonte: SCJP Sun Certified Programmer for Java 6 Study Guide

Autoboxing


Antes do Java 5, para incrementar um valor de um objeto wrapper era necessário extrair esse valor para uma variável primitiva, incrementar o valor e instanciar novamente o objeto com o novo valor, pois classes wrapper são imutáveis. Hoje, com o recurso Autoboxing, o compilador faz isso internamente, ou seja, é possível incrementar diretamente o objeto wrapper.

public class Java4_Java5 {
      public static void main(String[] args) {
            /* no Java 4 não é possível incrementar
             * objetos wrapper; um modo de burlar
             * isso é a seguinte forma: */
            Integer y = new Integer(1);
            int x = y.intValue();
            x++;
            y = new Integer(x);
            System.out.println(y); // imprime 2
           
            /* no Java 5 existe um recurso chamado
             * autoboxing que faz isso
             * automaticamente, ou seja, incrementa
             * objetos wrapper */
            Integer a = new Integer(2);
            a++;
            System.out.println(a); //imprime 3
      }    
}

Obs: quando o compilador utiliza o autoboxing, na verdade ele não incrementa um objeto wrapper, mas sim reatribui um novo valor já incrementado para aquele objeto; ou seja, sempre se tratará de uma nova referência.

Boxing, == e equals()

A intenção do método equals() é determinar se duas instâncias de uma dada classe são equivalentes; para todas as classes wrapper, dois objetos são “equals” se eles são do mesmo tipo e possuem o mesmo valor; ou seja, se os dois objetos wrapper tiverem o mesmo valor primitivo, o equals() os considerará equivalentes e retornará true.
Duas instâncias de uma classe wrapper sempre serão “==” quando seus valores primitivos forem o mesmo e forem menores que 127, se forem maiores serão diferentes, mesmo se tratando de valores iguais.

public class Equals {  
      public static void main(String[] args) {
            Integer int1 = 200;
            Integer int2 = 200;
            /* não são iguais porque, apesar de
             * possuirem o mesmo valor, são menores
             * que 127 */
            if(int1 == int2)
                  System.out.println("==");
            else
                  System.out.println("!=");
            /*são equals porque possuem o mesmo valor*/
            if(int1.equals(int2))
                  System.out.println("equals");
            else
                  System.out.println("não equals");
      }
}

Obs.: classes wrapper podem ser null, portanto, antes de utilizar operações de primitivos com esses objetos deve-se verificar se este está instanciado.



Fonte: SCJP Sun Certified Programmer for Java 6 Study Guide