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?