sexta-feira, 24 de setembro de 2010

OutOfMemoryError (??)

Embora o título desse post possa parecer estranho, ele refere-se a um OutOfMemoryError que pode ocorrer em uma situação (bem específica é verdade), mas que aparentemente não deveria. Sem mais delongas, segue o código:


public class Foo {

public static void exec(int size) {
{
byte[] someData = new byte[size];
process(someData);
}
//final do bloco
byte[] someData = new byte[size];
process(someData);
System.out.println("ok");
}

private static void process(byte[] data) {
}

public static void main(String[] args) {
exec((int) (0.7 * Runtime.getRuntime().maxMemory()));
}
}


O código acima lança uma OutOfMemoryError: java heap space, muito embora não devesse, já que o objeto 'someData' sai de escopo após a linha 7, quando o bloco termina. A primeira impressão que pode ficar é, como as duas alocações estão próximas, não dá tempo para o GC limpar a memória. Tentaremos, então, chamar explicitamente o GC para ser executado no meio tempo:



public class Foo {

public static void exec(int size) {
{
byte[] someData = new byte[size];
process(someData);
}
//final do bloco
System.gc();
byte[] someData = new byte[size];
process(someData);
System.out.println("ok");
}

private static void process(byte[] data) {
}


public static void main(String[] args) {
exec((int) (0.7 * Runtime.getRuntime().maxMemory()));
}
}


Mesmo desse jeito, o código continua com o OutOfMemoryError. Algumas pessoas familiarizadas com o funcionamento do GC podem afirmar que chamar System.gc() não necessariamente implica no GC ser executado de verdade. Mas o código abaixo mostra(?) que, se formos insistentes, a memória é coletada apropriadamente:



public class Foo {

public static void exec(int size) {
{
byte[] someData = new byte[size];
process(someData);
}
//final do bloco
for(int i = 0; i < 10; i++) System.gc();

byte[] someData = new byte[size];
process(someData);
System.out.println("ok");
}

private static void process(byte[] data) {
}

public static void main(String[] args) {
exec((int) (0.7 * Runtime.getRuntime().maxMemory()));
}
}


E também podemos ver que gerenciando "manualmente" a memória (ou seja, anulando a variável someData ao final do bloco), o código funciona normalmente:



public class Foo {

public static void exec(int size) {
{
byte[] someData = new byte[size];
process(someData);
someData = null;
}
//final do bloco
byte[] someData = new byte[size];
process(someData);
System.out.println("ok");
}

private static void process(byte[] data) {
}


public static void main(String[] args) {
exec((int) (0.7 * Runtime.getRuntime().maxMemory()));
}
}


Por que será que se insistirmos, o problema some? Mais do que isso, há um problema com o gerenciamento de memória da VM? Devemos esquecer que o GC existe e gerenciar a coleta de objetos manualmente, anulando todas as variáveis quando elas deixam de ser usadas?

3 comentários:

  1. HAHAHAHHA
    Isso é tipo uma pegadinha?
    O primeiro 'someData' nunca sai do escopo porque está dentro de um bloco em um contexto estático, logo ele nunca é coletado, pois 'teoricamente' está sendo usado. Afinal se trata de uma referência normal.
    Quando se anula a referência a VM entende que não será mais utilizada.

    ResponderExcluir
  2. Quase uma pegadinha =P.
    Mas o final do contexto faz sim com que a variavel saia de escopo e seja coletada - ou pelo menos deveria.
    De qualquer modo, isso não explicaria o 3o caso - o que funciona com o System.gc() dentro de um for.

    ResponderExcluir
  3. Verdade... o 3o exemplo funciona... parece que ele está se perdendo com o gc nos blocos, como se tivesse postergando a coleta... vou tentar dah uma procurada sobre como ele trata esse tipo de situação... bizarro! =)

    ResponderExcluir