1 de fev. de 2011

Strings no Java


Aproveitando a conversa com o Bruno sobre equals e == queria expor uma situação interessante. O modo como o Java lida com Strings tem sua particularidades que estão diretamente ligadas ao gerenciamento de memória. Por exemplo, no caso abaixo, a primeira assertiva é verdadeira, já que "aaa" é igual a "aaa", e a segunda assertiva é falsa, mesmo digitando "aaa" em "Outro texto". Se o operador == for substituído pelo método equals ambas assertivas retornam verdadeiro. Por quê?

public class Main {
    public static void main(String[] args) {

        String texto = "aaa";

        System.out.println("texto == aaa: " + (texto == "aaa") );

        System.out.print("Outro texto: ");

        String outroTexto = new Scanner(System.in).nextLine();

        System.out.println("texto == outroTexto: " + (texto == outroTexto) );

    }
}


O segredo está na arquitetura da memória no Java, embora existam algumas estratégias diferentes dependendo da implementação da máquina virtual, como a Java Hotspot, Oracle JRockit e o Apache Harmony.

Basicamente temos a memória divida como ...

  • ... uma pequena área chamada Stack (pilha), onde ficam as variáveis locais e chamadas de métodos (onde são empilhados). Por isso quando fazemos um encadeamento muito longo de chamadas, ou um processamento recursivo muito profundo, recebemos o erro: StackOverflowError. O Bruno teve um erro assim no C#;
  • ... um espaço maior, que é a memória disponível para os objetos, chamada Heap. Geralmente o Heap é divido em Young e Tenured (ver imagem abaixo), ou seja, objetos jovens ficam em parte de memória e se sobrevirem ao coletor de lixo são movidos para maduros (tenured), onde o intervalo de tempo para coleta é maior.
  • ... um espaço chamado Permanent Generation (geração permanente), onde ficam as informações carregadas que serão usadas durante todo o tempo de vida da aplicação, desde aberta até fechada. Nesta área ficam as classes por exemplo e outros dados estáticos, e também, e aí está o segredo, as String literais.


Toda String literal no programa, como o "aaa" no código, são instanciadas (new) e armazenadas no PermGen (não no Heap). Assim, quando novamente usamos "aaa", literalmente no código, ele utiliza aquela instância no PermGen. Isto é uma técnica para melhorar a performance e economizar memória, e foi por esse motivo que comparando "aaa" == "aaa" devolve verdadeiro, pois ambos apontam para a mesma String na memória. Agora no caso de usar new String("aaa") (como observou o Paulo), ou ler uma String de uma base de dados, console, parâmetro de uma requisição HTTP, enfim, tudo que não for literal, nós temos uma nova instância.

O resumo da ópera é, como disse o Leonardo, devemos usar o equals, que é implementado para comparar os atributos e não a posição na memória. No C#, é possível usar o operador == sempre, por que ele é sobrescrito na classe String para executar o método equals, ou seja, quando eu faço no C# "aaa" == "aaa", ele faz "aaa".equals("aaa").

Só mais um detalhe, se alguém aqui cria aplicações Web no Java e levanta no Tomcat, Glassfish, JBoss ou Jetty, já deve ter percebido que em desenvolvimento se for fazendo vários deploys, uma hora ou outra, tem que para o servidor e iniciar outra vez por que dá OutOfMemoryError: PermGen space. É por quê cada vez que a aplicação é implantada (deployada é horrível), as classes e Strings literais são novamente colocadas no PermGen e ele acaba estourando.

Algumas leituras sugeridas são:

The Structure of the Java Virtual Machine: http://java.sun.com/docs/books/jvms/second_edition/html/Overview.doc.html

Java HotSpot VM Options: http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

Entendendo o NoSuchMethodError e o ClassLoader hell: http://www.arquiteturajava.com.br/livro/entendendo-nosuchmethoderror-e-classloader-hell.pdf

Começando com parâmetros e configurações da JVM: http://blog.caelum.com.br/comecando-com-parametros-e-configuracoes-da-jvm/

Mão sei se me fiz entender. Já passei por alguns problemas de performance e estabilidade com Java, por isso conheço um pouco dessa arquitetura.

Abraço a todos

Nenhum comentário: