[Resolvido]Como esperar o Garbage Collector passar

Bom dia. Eu estou desenvolvendo uma aplicação que faz a manipulação de grandes informações de texto em um banco de dados. O problema, é que tenho um laço que faz inserções e ele gera muito lixo a cada iteração. Com tempo passei a ter o famoso erro de java.lang.OutOfMemoryError: Java heap space, ou seja, o espaço do heap se esgotou. A primeira alternativa para solucionar isso foi chamar o GC ao final de cada iteração com:

System.gc();

Sei que isso não é garantia de que ele irá passar, mas requisitando ele com grande frequencia, resolveu meu problema. Mas a aplicação ficou insuportavelmente lenta. Dai pensei, vou monitorar o espaço livre no heap, com:

long tamanhoHeap = Runtime.getRuntime().totalMemory(); long tamanhoMaxHeap = Runtime.getRuntime().maxMemory(); long tamanhoLivreHeap = Runtime.getRuntime().freeMemory();

para que quando ele chegue a um limite próximo do mínimo, eu chame o GC, melhorando assim a performace e diminuindo as chamadas desnecessárias. Algo do tipo:

if(tamanhoLivreHeap < algumTamanho){ System.gc(); }
Deu java.lang.OutOfMemoryError: Java heap space de novo, pois o GC não passava naquele exato momento.

Tentei então uma terceira alternativa, que seria a cada iteração, verificar a quantidade de espaço livre, e se ela estiver abaixo de um patamar, segurar a execução em um laço, até que o GC passe e libere mais memória. Algo do tipo:

while(tamanhoLivreHeap < algumTamanhoX);

Mas isso congela a aplicação. Alguém teria mais alguma sugestão?

Desde já agradeço!

Que tal usar um outro algoritmo de garbage collection, e tunar os parâmetros?

Por exemplo, se você usava java -cp . SeuPrograma parametros, você faria algo como:

java -XX:+UseG1GC -cp . SeuPrograma

ou então

java -Xincgc -cp . SeuPrograma

ou então

java -XX:+UseParallelOldGC -cp . SeuPrograma

Veja qual é que funciona melhor para você.

Aumenta o heap com aquele parametro inicial do Java ou não cria tanto lixo. Reutilize os seus objetos.

Até daria para fazer o seu thread dormir enquanto a sua freeMemory() não aumenta, Ultimo caso tenta isso.

Vc tem 3 opções… Se seu processo ta gastando mais memoria do que a alocada para JVM…

1) Aumentar a memoria
Complicado aqui…uma vez que quanto maior o texto, mais RAM na JVM vc vai precisar…então na verdade não resolve…apenas remedia temporariamente.

2) Reduzir os gastos
Aqui ja podemos caminhar melhor…
Por exemplo…
1)use JDBC BATCH…
2)use StringBuffer…
3)use paginação no processos ai…
4)Aplicar singleton para objetos compartilhados
5)liberar todos os ponteiros logo apos uso.

3) Fazer o passo 1 e passo1
Mesmo vc otimizando todo o possível, ainda sim seu processo ainda pode estar gastando mais memoria do que o disponivel. Por isso vc ainda tera que aumentar a memoria.

OBS - não vai adiantar esperar o GC passar se vc ta gastando mais do q vc tem…é questão de matemática simples.

Bom, na minha opinião, isso é caso para otimizar o algoritmo. Talvez você possa utilizar alguma abordagem divide-and-conquer, e processar os dados em partes.

Você nem precisa ficar forçando o gc a rodar,
o gc sempre roda quando um ‘pedaço’ do seu heap esta cheio de referências…
O melhor seria se você garantisse ou otimizasse ao máximo esse grande número de objetos que você esta mantendo alguma referência porque ele só trava quando não tem mais como se livrar desses caras mesmo…

[quote=grunfeldt]Você nem precisa ficar forçando o gc a rodar,
o gc sempre roda quando um ‘pedaço’ do seu heap esta cheio de referências…
O melhor seria se você garantisse ou otimizasse ao máximo esse grande número de objetos que você esta mantendo alguma referência porque ele só trava quando não tem mais como se livrar desses caras mesmo…[/quote]
Corretíssimo…

Não existe regra, mas quando o HEAP esta em media de 70% o GC ja começa a se movimentar para liberar…

Resumindo:
Trabalhar com memoria no Java é como uma brincadeira de criança…

  • a memoria é copo com um tamanho determinado.
  • cada vez vc cria um objeto (new) vc coloca um gota de água nesse copo.

Como funciona?

  • Quando vc inicia a aplicação, vc ja coloca um pouco de água…tipo uns 20% do copo (pelos gastos do objetos iniciais)
  • Enquanto a aplicação ta rodando, que vai enchendo de água…(gastando objeto durante o uso)
  • Quando a água começa ficar acima dos 70% , o GC ja vai tentando retirar/derramar essa agua do copo (retirar os objetos sem referencia)
  • Se o gc conseguir ele, esvazia esse copo…tudo volta ao ciclo…
  • Se o gc não conseguir…a agua cai do copo java.lang.OutOfMemoryError kkkkk

Motivos de Falta de Memoria?

  1. A solução esta gastando memoria desenfreadamente - falta de uma arquitetura correta ou desenvolver despreparado.
    Como corrigir? Abrir o código e otimizar. Existem muitas e muitas práticas…

2 A solução esta gastando memoria corretamente, mas ainda não é suficiente.
Se a aplicação esta ok e otimizada…unica coisa q pode ser feito é aumentar a memoria…pq a solução precisa de mais memoria!!!
Acontece quando muito principalmente em aplicações web quando o numero de usuário simultâneo vai aumento.

[quote=grunfeldt]Você nem precisa ficar forçando o gc a rodar,
o gc sempre roda quando um ‘pedaço’ do seu heap esta cheio de referências…
O melhor seria se você garantisse ou otimizasse ao máximo esse grande número de objetos que você esta mantendo alguma referência porque ele só trava quando não tem mais como se livrar desses caras mesmo…[/quote]

Bem, é o contrário, de acordo com os artigos que li. Enquanto os endereços estão referenciados eles são a “velha geração”, e são alocados no heap consumindo espaço. Não existe gato no planeta que faça o gc liberar essa memória, até porque ela está referenciada.

Uma maneira de forçar o coletor de lixo é atribuir null ao endereço. Como não há mais referência o ele já está marcado para ser coletado.

isso aqui é a “marca da morte”

Object o = new Object(); o = null;

diz que o pode ser coletado.

A solução do entanglement é muito boa. Ela pode resolver seu problema. No mais tente melhorar a sua lógia e reaproveitar mais os objetos alocados ao invés de coletá-los.

[quote=entanglement]Que tal usar um outro algoritmo de garbage collection, e tunar os parâmetros?

Por exemplo, se você usava java -cp . SeuPrograma parametros, você faria algo como:

java -XX:+UseG1GC -cp . SeuPrograma

ou então

java -Xincgc -cp . SeuPrograma

ou então

java -XX:+UseParallelOldGC -cp . SeuPrograma

Veja qual é que funciona melhor para você.

[/quote]

Cara, tentei os 3, nenhum deu certo… Acho que meu problema não é a frequência com que o GC passa, e sim o monte de lixo que estou deixando na memória…

[quote=saoj ]Aumenta o heap com aquele parametro inicial do Java ou não cria tanto lixo. Reutilize os seus objetos.

Até daria para fazer o seu thread dormir enquanto a sua freeMemory() não aumenta, Ultimo caso tenta isso.

[/quote]

Eu aumentei o tamanho do heap com -Xms192m -Xmx1024m, dai, ele rodou mais tempo, mas deu o mesmo erro de estouro de heap… Tentei também a estratégia do Thread.sleep() e não deu certo…

[quote=rmendes08 ]Bom, na minha opinião, isso é caso para otimizar o algoritmo. Talvez você possa utilizar alguma abordagem divide-and-conquer, e processar os dados em partes.
[/quote]

Na verdade, é isso mesmo que estou fazendo… Estou implementando um sistema distribuído…

[quote=FernandoFranzini ]Vc tem 3 opções… Se seu processo ta gastando mais memoria do que a alocada para JVM…
[/quote]

Acho que estou mesmo é gastando referências de mais… Deixo o código abaixo… Quem puder da um força plz :oops:

[code]while ((linha = leitor.readLine()) != null) {
linha = linha.trim();
nrolinha++;
if (linha.startsWith(">")) {
pst = con.prepareStatement(“SELECT intCabecalho FROM tbCabecalho where strCabecalho = ?”);
pst.setString(1, linha);
rs = pst.executeQuery();
if (rs.next() == false) {
pst = con.prepareStatement(“INSERT INTO tbCabecalho (strCabecalho) values (?)”);
pst.setString(1, linha);
pst.executeUpdate();
pst = con.prepareStatement(“SELECT intCabecalho FROM tbCabecalho where strCabecalho = ?”);
pst.setString(1, linha);
rs = pst.executeQuery();
rs.next();
idCabecalho = rs.getInt(“intCabecalho”);
} else {
idCabecalho = rs.getInt(“intCabecalho”);

                    }

                } else {
                    sequencia.append(linha);
                    leitor.mark(nrolinha);
                    proximalinha = leitor.readLine();
                    if (proximalinha == null || proximalinha.startsWith(">")) {

                        pst = con.prepareStatement("SELECT intSequencia FROM tbSequencia where strSequencia = ?");
                        pst.setString(1, sequencia.toString());
                        rs = pst.executeQuery();


                        if (rs.next() == false) {
                            pst = con.prepareStatement("INSERT INTO tbSequencia (strSequencia) values (?)");
                            pst.setString(1, sequencia.toString());
                            pst.executeUpdate();
                            pst = con.prepareStatement("SELECT intSequencia FROM tbSequencia where strSequencia = ?");
                            pst.setString(1, sequencia.toString());
                            rs = pst.executeQuery();
                            rs.next();
                            idSequencia = rs.getInt("intSequencia");
                            sequencia = new StringBuilder();
                        } else {
                            idSequencia = rs.getInt("intSequencia");
                            sequencia = new StringBuilder();
                        }
                        pst = con.prepareStatement("INSERT INTO tbCabecalhoFonteSequencia"
                                + " values (?,?,?)");
                        pst.setInt(1, idCabecalho);
                        pst.setInt(2, idFonte);
                        pst.setInt(3, idSequencia);
                        pst.executeUpdate();
                        

                        long tamanhoHeap = Runtime.getRuntime().totalMemory();
                        long tamanhoMaxHeap = Runtime.getRuntime().maxMemory();
                        long tamanhoLivreHeap = Runtime.getRuntime().freeMemory();

                        System.out.println("Tamanho Heap: " + tamanhoHeap);
                        System.out.println("Tamanho Máx Heap: " + tamanhoMaxHeap);
                        System.out.println("Tamanho Livre Heap: " + tamanhoLivreHeap);

                    }
                    leitor.reset();
                }
            }[/code]

Te feio mesmo em amigo…
Nem PreparetedStatement vc ta reusando…ta criando todo hora afffff…dai não
Outra coisa…vc deve usar BATCH…

[quote=FernandoFranzini]Te feio mesmo em amigo…
Nem PreparetedStatement vc ta reusando…ta criando todo hora afffff…dai não
Outra coisa…vc deve usar BATCH…[/quote]

Opa, pela forma que eu fiz eu achei que estava reusando, pelo fato de não fazer new toda hora :oops: … Como faço para mudar? De que se trata BATCH? :oops:

dá uma repassada nas suas listas.

se você usa add sem remove ela vai crescer desordenadamente como deve ser mesmo.

Se dá OutOfMemoryError é porque necessariamente, você tem objetos referenciados. O motivo não é por culpa do GC, ou porque o GC deixou de rodar.
O GC sempre rodará antes de dar um OutOfMemoryError.

Portanto, chamadas a System.gc() são completamente inúteis nesse contexto. Seus usos são muito raros, geralmente para preparar a VM para um processamento de tempo real.

No seu caso, eu começaria limpando a bagunça que está seu código e também usaria o profiler para identificar o gargalo.
Também reveria com cuidado essas consultas aí, especialmente porque não estou vendo nenhum comando de “close” em seus statements.

Claro q não…leia o JAVADOC do metodo…

Reusar o prepareted é criar uma vez…

realmente vc ta fazendo da pior forma…

pst = con.prepareStatement("INSERT INTO tbCabecalho (strCabecalho) values (?)"); // Aqui vc cria 1 vez e reusa.....para que criar denovo se é o mesmo sql? pst.setString(1, VALOR_1); pst.executeUpdate(); pst.setString(1, VALOR_2); pst.executeUpdate(); pst.setString(1, VALOR_2); pst.executeUpdate(); 3 comando para o banco......

De qualque forma…vc deve usar BATCH - http://www.jdbctutorial.net/jdbc-batch-update-oracle.php
Vai melhorar, muito mas ainda pode estorar…não tem como eu garantir…

Na sua abordagem vc ta gastando muito objetos JDBC e esta fazendo um viagem TPC a cada SQL…
Cada uma abre a transação automática do JDBC, a não ser q vc configurou ao contrario…
No BATCH vc vai preencher todos os SQL e vai ir no banco 1 vez por transação, executando TODOS sql de um unica vez!

Seguindo as dicas do FernandoFranzini e do ViniGodoy, funcionou belezinha. Relmente, a cada iteração do laço eu gerava mais lixo. O código ficou assim:

[code]public static void inserirUltimoArquivo() {

    try {

        Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/BaseBiologica", "root", "root");
        Class.forName("com.mysql.jdbc.Driver");
        ResultSet rs;
        int idFonte = 0, idCabecalho = 0, idSequencia = 0;

        PreparedStatement pst1 = con.prepareStatement("INSERT INTO tbFonte (strSite, strDescricao) values (?, ?)");
        PreparedStatement pst2 = con.prepareStatement("SELECT intFonte FROM tbFonte where strDescricao = ?");
        PreparedStatement pst3 = con.prepareStatement("SELECT intCabecalho FROM tbCabecalho where strCabecalho = ?");
        PreparedStatement pst4 = con.prepareStatement("INSERT INTO tbCabecalho (strCabecalho) values (?)");
        PreparedStatement pst5 = con.prepareStatement("SELECT intSequencia FROM tbSequencia where strSequencia = ?");
        PreparedStatement pst6 = con.prepareStatement("INSERT INTO tbSequencia (strSequencia) values (?)");
        PreparedStatement pst7 = con.prepareStatement("INSERT INTO tbCabecalhoFonteSequencia values (?,?,?)");

        try {
            pst1.setString(1, frmServidor.listaArquivos.get(frmServidor.listaArquivos.size() - 1).getName().split("-")[0].trim());
            pst1.setString(2, frmServidor.listaArquivos.get(frmServidor.listaArquivos.size() - 1).getName());
            pst1.execute();
            pst1.close();

            pst2.setString(1, frmServidor.listaArquivos.get(frmServidor.listaArquivos.size() - 1).getName());
            rs = pst2.executeQuery();
            rs.next();
            idFonte = rs.getInt("intFonte");
            pst2.close();


            BufferedReader leitor = new BufferedReader(new FileReader(frmServidor.listaArquivos.get(frmServidor.listaArquivos.size() - 1)));
            String linha;
            String proximalinha;
            StringBuilder sequencia = new StringBuilder();
            int nrolinha = 0;
            while ((linha = leitor.readLine()) != null) {
                linha = linha.trim();
                nrolinha++;
                if (linha.startsWith(">")) {

                    pst3.setString(1, linha);
                    rs = pst3.executeQuery();
                    if (rs.next() == false) {
                        pst4.setString(1, linha);
                        pst4.executeUpdate();
                        

                        pst3.setString(1, linha);
                        rs = pst3.executeQuery();
                        rs.next();
                        idCabecalho = rs.getInt("intCabecalho");
                        

                    } else {
                        idCabecalho = rs.getInt("intCabecalho");
                    }

                } else {
                    sequencia.append(linha);
                    leitor.mark(nrolinha);
                    proximalinha = leitor.readLine();
                    if (proximalinha == null || proximalinha.startsWith(">")) {
                        pst5.setString(1, sequencia.toString());
                        rs = pst5.executeQuery();
                        if (rs.next() == false) {
                            pst6.setString(1, sequencia.toString());
                            pst6.executeUpdate();
                            

                            pst5.setString(1, sequencia.toString());
                            rs = pst5.executeQuery();
                            rs.next();
                            idSequencia = rs.getInt("intSequencia");
                            sequencia = new StringBuilder();
                            
                        } else {
                            idSequencia = rs.getInt("intSequencia");
                            sequencia = new StringBuilder();
                        }

                        pst7.setInt(1, idCabecalho);
                        pst7.setInt(2, idFonte);
                        pst7.setInt(3, idSequencia);
                        pst7.executeUpdate();
                        

                        long tamanhoHeap = Runtime.getRuntime().totalMemory();
                        long tamanhoMaxHeap = Runtime.getRuntime().maxMemory();
                        long tamanhoLivreHeap = Runtime.getRuntime().freeMemory();

                        System.out.println("Tamanho Heap: " + tamanhoHeap);
                        System.out.println("Tamanho Máx Heap: " + tamanhoMaxHeap);
                        System.out.println("Tamanho Livre Heap: " + tamanhoLivreHeap);
                    }
                    leitor.reset();
                }
            }
        } catch (SQLException ex) {
            ex.printStackTrace();
        }
    } catch (Exception exx) {
        exx.printStackTrace();
    }
    exportarBD("BaseBiologica", "Scripts_BDs/BD" + listaVetTrabalho.get(listaVetTrabalho.size() - 1).getPath().trim().charAt(0));
    String BDPronto = frmServidor.listaArquivos.get(frmServidor.listaArquivos.size() - 1).getPath();
    listaBDsProntos.add(BDPronto);
    ultimoArq = true;
    txtAStatusServidor.setText(txtAStatusServidor.getText() + "Conteúdo do útimo arquivo da lista inserido no banco de "
            + "dados com sucesso!\nExportando versão do banco de dados contendo último arquivo da lista...\n");
    txtAStatusServidor.setText(txtAStatusServidor.getText() + "Exportação concluída com sucesso!\n");

}[/code]

Agora sim…ta melhorando…veja que é por isso que existe comando pre compilados no banco…justamente pq vc não ficar enviando e compilando o mesmo comando varias vezes…
Não esta mais estourando? Melhoro quanto?
Acho que depois q vc aplicar o BATCH…vai ficar 100%.

[quote=FernandoFranzini]Agora sim…ta melhorando…veja que é por isso que existe comando pre compilados no banco…justamente pq vc não ficar enviando e compilando o mesmo comando varias vezes…
Não esta mais estourando? Melhoro quanto?
Acho que depois q vc aplicar o BATCH…vai ficar 100%.[/quote]
O consumo de memória foi de quase 1 GB para 36 MB! Qto por cento dá isso? :lol: hehehehe obrigado mesmo pela força!!! Quanto ao BATCH quero sim aprender a como usar, mas o problema é que estou meio apertado com o prazo da monografia… Se sobrar tempo no final, eu volto e faço os aperfeiçoamentos (esse do BATCH e mais um monte que quero fazer!)! =D