Java Heap Space em consultas ao banco

5 respostas
thiagofesta

Bom dia a todos,

Estou com um sério problema, eu estou fazendo um artigo e uma parte dele é onde faço alguns testes de desempenho, com 3 bancos de dados, o Apache Derby, MySQL e PostgreSQL.

O a estrutura das tabelas são iguais em todos os bancos, existe a tabela Pessoa (possui FK de endereco), e a Endereço, e ambas possuem um índice no nome da pessoa e na descrição do endereço.

São testes: inserção de 1 milhão de registros em cada tabela. busca destes 1 milhão de registros das duas tabelas, e depois com um JOIN.

Com o Apache Derby eu fiz todos estes teste e deu certo.
Com o MySQL chega a cerca de 10 mil registros na tabela Endereço ai já da Java Heap Space.
Com o PostgreSQL ele inseriu nas tabelas, mas não buscou.

Ai eu pensei em aumentar a memória do Java de 128mb até 800mb.
Ai no PostgreSQL deu certo, porém não deu certo a exclusão dos 2 milhões de registros; E no MySQL travou o pc.

O que pode ser?

Segue abaixo as duas classes que uso nos testes.

package br.com.thiagofesta.banco;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Conexao {

    private Connection connection;
    private String banco;

    public Conexao(String banco) {
        try {
            this.banco = banco;
            if(banco.equalsIgnoreCase("derby")) {
                connection = DriverManager.getConnection("jdbc:derby:/home/thiago/Faculdade/MPC/Artigo Apache Derby/derby");
            } else if(banco.equalsIgnoreCase("mysql")) {
                connection = DriverManager.getConnection("jdbc:mysql://localhost/mpc?user=teste&password=123456");
            } else if(banco.equalsIgnoreCase("postgresql")) {
                connection = DriverManager.getConnection("jdbc:postgresql://localhost:5432/mpc", "teste", "123456");
            }

            connection.setAutoCommit(false);
        } catch(SQLException e) {
            e.printStackTrace();
        }
    }

    public Connection getConnection() {
        return this.connection;
    }

    public ResultSet executeQuery(String query) throws SQLException
    {
       return this.connection.createStatement().executeQuery(query);
    }

    public void executeUpdate(String query) throws SQLException
    {
        this.connection.createStatement().executeUpdate(query);
    }

    public String getBanco() {
       return this.banco;
    }

}
package br.com.thiagofesta.form;

import br.com.thiagofesta.banco.Conexao;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Programa extends javax.swing.JFrame {

    private Conexao conexao = null;
    private String current = "";

    public Programa() {
        initComponents();
        jLTempo.setVisible(false);
        jLRegistros.setVisible(false);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jLBanco = new javax.swing.JLabel();
        jCBBanco = new javax.swing.JComboBox();
        jLOperacao = new javax.swing.JLabel();
        jCBOperacao = new javax.swing.JComboBox();
        jBIniciar = new javax.swing.JButton();
        jLTempo = new javax.swing.JLabel();
        jBConectar = new javax.swing.JButton();
        jLRegistros = new javax.swing.JLabel();
        jTQtdRegistros = new javax.swing.JTextField();
        jLQtdRegistros = new javax.swing.JLabel();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("MPC");

        jLBanco.setText("Banco de dados:");

        jCBBanco.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Derby", "MySQL", "PostgreSQL" }));

        jLOperacao.setText("Operação:");

        jCBOperacao.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Inserção na tabela Pessoa", "Inserção na tabela Endereço", "Inserção nas tabelas Pessoa e Endereço", "Busca de todos os dados (Pessoa)", "Busca de todos os dados (Endereço)", "Busca de todos os dados com junção (Pessoa e Endereço)", "Exclusão de todos os registros" }));

        jBIniciar.setText("Executar");
        jBIniciar.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBIniciarActionPerformed(evt);
            }
        });

        jLTempo.setText("Tempo gasto:");

        jBConectar.setText("Conectar Banco");
        jBConectar.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBConectarActionPerformed(evt);
            }
        });

        jLRegistros.setText("jLabel1");

        jLQtdRegistros.setText("Qtd. Registros:");

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jLRegistros, javax.swing.GroupLayout.DEFAULT_SIZE, 676, Short.MAX_VALUE)
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                                .addComponent(jBConectar, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addComponent(jCBBanco, 0, 135, Short.MAX_VALUE))
                            .addComponent(jLBanco))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(layout.createSequentialGroup()
                                .addComponent(jBIniciar, javax.swing.GroupLayout.PREFERRED_SIZE, 123, javax.swing.GroupLayout.PREFERRED_SIZE)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(jLTempo, javax.swing.GroupLayout.DEFAULT_SIZE, 394, Short.MAX_VALUE))
                            .addGroup(layout.createSequentialGroup()
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                    .addComponent(jLOperacao)
                                    .addComponent(jCBOperacao, 0, 392, Short.MAX_VALUE))
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                    .addComponent(jTQtdRegistros, javax.swing.GroupLayout.PREFERRED_SIZE, 131, javax.swing.GroupLayout.PREFERRED_SIZE)
                                    .addGroup(layout.createSequentialGroup()
                                        .addGap(6, 6, 6)
                                        .addComponent(jLQtdRegistros)))))))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(jLBanco)
                            .addComponent(jLOperacao))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(jCBBanco, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(jCBOperacao, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(jLQtdRegistros)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jTQtdRegistros, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                        .addComponent(jBIniciar)
                        .addComponent(jBConectar))
                    .addComponent(jLTempo, javax.swing.GroupLayout.PREFERRED_SIZE, 31, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLRegistros)
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void jBIniciarActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBIniciarActionPerformed


        if( (jCBOperacao.getSelectedIndex() == 0) || (jCBOperacao.getSelectedIndex() == 1) || (jCBOperacao.getSelectedIndex() == 2)) {

            if(jTQtdRegistros.getText().replaceAll(" ", "").length() <= 0) {
                System.out.println("É necessário preencher a quantidade de registros válida.");
                return;
            }

            try {
                Integer.parseInt(jTQtdRegistros.getText().replaceAll(" ", ""));
            } catch(Exception e) {
                System.out.println("É necessário preencher a quantidade de registros válida.");
                return;
            }
        }

        jLRegistros.setVisible(true);
        jLRegistros.setText("Nenhum registro(s)");

        jLTempo.setVisible(true);
        jLTempo.setText("Executando...");

        new Thread() {
            @Override
            public void run() {
                String SQL;
                ResultSet cursor;
                long inicio = System.currentTimeMillis();

                try {

                    if(jCBOperacao.getSelectedIndex() == 0)
                    {
                        //Inserção de 2 milhões de registros (Pessoa)
                        int total = Integer.parseInt(jTQtdRegistros.getText().replaceAll(" ", ""));
                        
                        for(int i = 1; i <= total; i++) {
                            SQL = "INSERT INTO tb_pessoa " +
                                   "    (id_endereco, nm_pessoa, ds_pessoa, " +
                                   "     tp_pessoa, nr_cpf_cnpj, nr_rg_ie, " +
                                   "     dt_cad, dt_alt) " +
                                   "    VALUES " +
                                   "    ("+ i +", 'Thiago Felipe Festa', 'hasodhashdhas dhas dih', " +
                                   "     'F', '[CPF removido]', '91000289', " +
                                   "     "+ current +", "+ current +")";
                            conexao.executeUpdate(SQL);

                            jLRegistros.setText(i +" registro(s)");
                            //try { sleep(500); } catch (InterruptedException ex) { ex.printStackTrace(); }
                        }

                    }
                    else if(jCBOperacao.getSelectedIndex() == 1)
                    {
                        //Inserção de 2 milhões de registros (Endereço)
                        int total = Integer.parseInt(jTQtdRegistros.getText().replaceAll(" ", ""));
                        
                        for(int i = 1; i <= total; i++) {
                            SQL = "INSERT INTO tb_endereco " +
                                   "    (ds_endereco, nr_casa, nm_bairro, " +
                                   "     nr_cep, nm_cidade, nr_telefone, " +
                                   "     nr_celular, dt_cad, dt_alt) " +
                                   "    VALUES " +
                                   "    ('Rua Dona Lúcia', 278, 'Jardim Modelo', " +
                                   "     '33333-333', 'Toledo', '45-4444-4444', " +
                                   "     '45-5555-5555', "+ current +", "+ current +")";
                            conexao.executeUpdate(SQL);

                            jLRegistros.setText(i +" registro(s)");
                        }
                    }
                    else if(jCBOperacao.getSelectedIndex() == 2)
                    {
                        int total = Integer.parseInt(jTQtdRegistros.getText().replaceAll(" ", ""));
                        
                        //Inserção de 4 milhões de registros (Pessoa e Endereço)
                        for(int i = 1; i <= total; i++) {
                            SQL = "INSERT INTO tb_endereco " +
                                   "    (ds_endereco, nr_casa, nm_bairro, " +
                                   "     nr_cep, nm_cidade, nr_telefone, " +
                                   "     nr_celular, dt_cad, dt_alt) " +
                                   "    VALUES " +
                                   "    ('Rua Dona Lúcia', 278, 'Jardim Modelo', " +
                                   "     '33333-333', 'Toledo', '45-4444-4444', " +
                                   "     '45-4444-4444', "+ current +", "+ current +")";
                            conexao.executeUpdate(SQL);

                            jLRegistros.setText(i +" registro(s)");
                        }

                        for(int i = 1; i <= total; i++) {
                            SQL = "INSERT INTO tb_pessoa " +
                                   "    (id_endereco, nm_pessoa, ds_pessoa, " +
                                   "     tp_pessoa, nr_cpf_cnpj, nr_rg_ie, " +
                                   "     dt_cad, dt_alt) " +
                                   "    VALUES " +
                                   "    ("+ i +", 'Thiago Felipe Festa', 'hasodhashdhas dhas dih', " +
                                   "     'F', '[CPF removido]', '91000289', " +
                                   "     "+ current +", "+ current +")";
                            conexao.executeUpdate(SQL);

                            jLRegistros.setText((i + total) +" registro(s)");
                        }
                    }
                    else if(jCBOperacao.getSelectedIndex() == 3)
                    {
                        //Busca de todos os dados (Pessoa)

                        SQL = "SELECT * FROM tb_pessoa";
                        cursor = conexao.executeQuery(SQL);
                    }
                    else if(jCBOperacao.getSelectedIndex() == 4)
                    {
                        //Busca de todos os dados (Endereço)

                        SQL = "SELECT * FROM tb_endereco";
                        cursor = conexao.executeQuery(SQL);
                    }
                    else if(jCBOperacao.getSelectedIndex() == 5)
                    {
                        //Busca de todos os dados com junção (Pessoa e Endereço)

                        SQL = "SELECT a.*, b.* " +
                              "  FROM tb_pessoa a " +
                              " INNER JOIN tb_endereco b ON a.id_endereco = b.id_endereco ";
                        cursor = conexao.executeQuery(SQL);
                    }
                    else if(jCBOperacao.getSelectedIndex() == 6)
                    {
                        SQL = "DELETE FROM tb_endereco";
                        conexao.executeUpdate(SQL);

                        if(conexao.getBanco().equalsIgnoreCase("derby")) {
                            SQL = "ALTER TABLE tb_endereco ALTER COLUMN id_endereco RESTART WITH 1";
                            conexao.executeUpdate(SQL);

                            SQL = "ALTER TABLE tb_pessoa ALTER COLUMN id_pessoa RESTART WITH 1";
                            conexao.executeUpdate(SQL);
                        } else if(conexao.getBanco().equalsIgnoreCase("postgresql")) {
                            SQL = "TRUNCATE tb_endereco CASCADE";
                            conexao.executeUpdate(SQL);

                            SQL = "TRUNCATE tb_pessoa";
                            conexao.executeUpdate(SQL);

                            SQL = "SELECT setval('tb_endereco_id_endereco_seq', 0, true)";
                            conexao.executeQuery(SQL);

                            SQL = "SELECT setval('tb_pessoa_id_pessoa_seq', 0, true)";
                            conexao.executeQuery(SQL);
                        } else {
                            SQL = "TRUNCATE tb_endereco";
                            conexao.executeUpdate(SQL);

                            SQL = "TRUNCATE tb_pessoa";
                            conexao.executeUpdate(SQL);
                        }

                    }

                    conexao.getConnection().commit();

                    long fim = System.currentTimeMillis() - inicio;
                    double segundos = fim / 1000.0;

                    jLTempo.setText("Tempo gasto: " + String.valueOf(segundos) + " segundos.");

                } catch(SQLException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }//GEN-LAST:event_jBIniciarActionPerformed

    private void jBConectarActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBConectarActionPerformed
        conexao = null;
        if(jCBBanco.getSelectedIndex() == 0) {
            conexao = new Conexao("derby");
            current = "CURRENT_TIMESTAMP";
        } else if(jCBBanco.getSelectedIndex() == 1) {
            conexao = new Conexao("mysql");
            current = "NOW()";
        } else if(jCBBanco.getSelectedIndex() == 2) {
            conexao = new Conexao("postgresql");
            current = "CURRENT_TIMESTAMP";
        }
    }//GEN-LAST:event_jBConectarActionPerformed

    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new Programa().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton jBConectar;
    private javax.swing.JButton jBIniciar;
    private javax.swing.JComboBox jCBBanco;
    private javax.swing.JComboBox jCBOperacao;
    private javax.swing.JLabel jLBanco;
    private javax.swing.JLabel jLOperacao;
    private javax.swing.JLabel jLQtdRegistros;
    private javax.swing.JLabel jLRegistros;
    private javax.swing.JLabel jLTempo;
    private javax.swing.JTextField jTQtdRegistros;
    // End of variables declaration//GEN-END:variables

}

Irei ser muito grato a quem ajudar, pois preciso concluir o artigo.

5 Respostas

ivela

Olá Thiago!!

O que acontece com a memória virtual do windows (ou o swap, no caso de Linux)?
Aparentemente, como a conexão não é commitada por instrução SQL, a JVM está armazenando as instruções em memória e está estourando por causa da quantidade.
Tente (sei que não é certo) commitar para cada instrução para ver se funciona (pelo menos garante a lógica do código).
Outra coisa: no MySQL (pelo que me lembro), as tabelas MyISAM não são transacionais - somente InnoDB.
Parece ser mais um problema de implementação do driver JDBC dos bancos do que outra coisa…

thiagofesta

alevi:
Olá Thiago!!

O que acontece com a memória virtual do windows (ou o swap, no caso de Linux)?
Aparentemente, como a conexão não é commitada por instrução SQL, a JVM está armazenando as instruções em memória e está estourando por causa da quantidade.
Tente (sei que não é certo) commitar para cada instrução para ver se funciona (pelo menos garante a lógica do código).
Outra coisa: no MySQL (pelo que me lembro), as tabelas MyISAM não são transacionais - somente InnoDB.
Parece ser mais um problema de implementação do driver JDBC dos bancos do que outra coisa…

Olá Alevi, uso Linux meu SWAP é de 1gb, mas vou tentar fazer como você disse, comitar a cada instrução.
Outra coisa, e porque será que com consulta do PostgreSQL tive que aumentar? Se você ver o código eu só consulto e guardo no ResultSet.

As tabelas do meu MySQL são InnoDB.

Agradeço desde já, logo mais posto o resultado do novo teste.

thiagofesta

Não deu certo!
:frowning:

Em anexo tem um print screen


ivela

Olá Thiago!!

Analisei seu código e fiz um teste com o MySQL. Apesar de não ter nada a ver com o problema, verifiquei alguns detalhes:

  • a porta do MySQL na abertura de conexão não está sendo informada (default como 3306);
  • a montagem da string para data (atributo “current” não estava recebendo um valor;
  • falta carregar a classe driver do MySQL com
Class.forName("com.mysql.jdbc.Driver");

no construtor da classe Conexao;

Bem, depois de acertado tudo, aí sim deu o Heap Space - :smiley:
Pesquisei na internet e vi alguns gringos que também tiveram este problema. Aparentemente, realmente se trata de limitações na implementação do driver JDBC para cada banco.

Dê uma olhada neste tópico: http://forums.mysql.com/read.php?39,152636,152761#msg-152761

Outra dica: utilize preparedStatement para evitar problemas na montagem da instrução SQL.

Espero ter ajudado…

Abraços!

thiagofesta

Alevi,
Obrigado pelas dicas, vou usá-las.

Apresentei ontem o artigo, a solução foi usar menos registros :frowning:

É uma pena ser limitado. Não comentei sobre isso na apresentação, mas é interessante comentar, vou ver se consigo comentar sobre isso nas próximas aulas.

Obrigado!

Criado 2 de junho de 2009
Ultima resposta 4 de jun. de 2009
Respostas 5
Participantes 2