OutOfMemoryError explicado
Autor: takeshi | Publicado: 2010-10-04T17:48:00-03:00

A resposta para o problema anterior passa por duas explicações:A pilha de execuçãoTodos os operadores na máquina virtual operam sobre elementos da pilha de execução. Isso significa que, por exemplo, a expressão abaixo:int x = 1 + 2;se torna algo como:empilha 1; // a pilha agora contem 1empilha 2; // a pilha agora contem 1, 2soma;  // os 2 elementos da pilha sao desempilhados e somados. A pilha contem 3guarda em x; //o 3 eh desempilhado e guardado em xParecido com assembly ASM.Variáveis locaisVariáveis locais (e argumentos de métodos) são guardados em uma "tabela" por índice, na ordem em que são declaradas em um método. Ou seja:public static void foo() {int i = 0;int j = 1;}A variável 'i' ocupa o índice 0 da tabela de variáveis locais e a variável 'j' ocupa o índice 1. Se o método é não estático, a referência ao objeto (o 'this') ocupa o índice 0. Se o método possui argumentos, ele ocupa os índices subsequentes. Assim sendo:public void foo(String arg0) {int i = 0;int j = 1;}O 'this' ocupa o índice 0, o 'arg0' ocupa o índice 1 e assim por diante. Quando uma variável local sai de escopo, o compilador reutiliza o índice daquela variável para a próxima declaração de variável local. Em outras palavras, no código abaixo:public static void foo() {{  int i = 0;}int j = 0;}a variável 'j' ocupa o índice '0' da tabela pois a variável 'i' saiu de escopo.O problemaSabendo disso, voltando ao problema original:  public static void exec(int size) {      {          byte[] someData0 = new byte[size];          process(someData);      }      //final do bloco      byte[] someData1 = new byte[size]; // #      process(someData);            System.out.println("ok");    }Quando declaramos a segunda variável local 'someData1', o código a ser executado fica mais ou menos assim (em pseudo bytecode):empilha size;  // a pilha contem size e o slot 1 contem o array antigo aindacria_array_bytes; // desempilha um inteiro e cria um array de bytes com o tamanho passadoguarda_em_1; // sobrescreve a variável local anteriorQuando a instrução de criar um novo array de bytes é executado, a JVM lança uma exceção dizendo que não há mais espaço. Isso significa que o código, em bytecode, fica parecido com:  public static void exec(int size) {          byte[] someData0 = new byte[size]; // #1      process(someData);          //final do bloco      someData0 = new byte[size]; // #2      process(someData);            System.out.println("ok");    }e o array criado em #1 só pode ser coletado depois que a instrução em #2 for executada - ou seja, tarde demais.Isso explica também porque os outros exemplos funcionam corretamente. Na verdade, podemos perceber que basta declarar uma variável local qualquer entre as criações dos arrays para que o primeiro array seja coletado. De fato, o código abaixo funciona corretamente:  public static void exec(int size) {      {          byte[] someData0 = new byte[size];          process(someData);      }      int j = 0;      //final do bloco      byte[] someData1 = new byte[size]; // #      process(someData);            System.out.println("ok");    }ConclusãoNão há nada de errado, tecnicamente, com o GC e nem com o código postado. Apenas (no bytecode) não estamos avisando que o primeiro array pode ser coletado, talvez isso devesse ser trabalho do compilador. Porém, na maioria dos casos, inserir uma instrução para anular as variáveis locais quando elas saem de escopo é desnecessário.PS:Estes artigos são meramente traduções/adaptações de um artigo que pode ser visto no java specialists newsletter. O artigo original e a resposta se encontram aqui e aqui.A versão JIT-compiled da classe funciona normalmente devido a alguma otimização da JVM. Isso pode ser visto ao executar a classe com o modo 'compiled' ao invés do modo híbrido (o default) usando a flag -Xcomp.Qualquer 'branching' (como um if ao invés do escopo vazio) vai fazer com que a JVM execute o código como esperado (sem a exceção), mesmo no modo interpretado.Por último, alguns GC's mais espertos (como o G1) também são capazes de identificar o primeiro array como lixo e coletar corretamente.