IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage Java Discussion :

OutOfMemoryError: Java heap space sur ResultSet


Sujet :

Langage Java

  1. #1
    Membre régulier
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    8
    Détails du profil
    Informations personnelles :
    Âge : 42
    Localisation : Suisse

    Informations forums :
    Inscription : Septembre 2008
    Messages : 8
    Par défaut OutOfMemoryError: Java heap space sur ResultSet
    Bonjour à tous,

    Pour mon premier post j'aimerais vous soumettre un bug qui méritera surement un coup de règle sur les doigts tellement c'était simple.

    But du code : Copier la totalité du contenu d'une table dans une base Oracle nommée 'bve' vers une table presque identique dans une base DB2 nommée 'xms'.
    Pour ce faire je crée un PreparedStatement du type 'insert into foobar(a,b,c...) values (?,?,?...)', et je boucle sur le resultat du 'select *' de la table source.
    A chaque tour je fais un setXxx sur chacun des '?', et quand mon insert est prèt à être exécuté je l'ajoute à un batch. Au bout de 100, je les execute, commite, et on est repartis pour un prochain lot de 100.

    Problème : Là où ça se gâte, c'est que j'ai 131 000 records de 50 colonnes à copier. Je me ramasse une 'java.lang.OutOfMemoryError: Java heap space' au bout de 29 100 records.

    Une solution est d'augmenter la mémoire de la JMV mais ce n'est que repousser le problème à plus tard. Ce n'est pas réellement une solution car au fur et à mesure les batch mettent plus de temps à s'executer.

    Est ce que qqun voit une faute flagrante dans mon algo ? Connaissez vous une meilleure façon de traiter le besoin ?
    J'ai essayé de setter à null insertStatement juste apres avoir commité puis d'appeler System.gc() pour tenter de nettoyer la memoire mais rien n'y fait.

    Merci pour votre clairvoyance.

    SuperFoieGras


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
     
    // Source est le nom de la table source, target celui de la table cible
    // bve est la connection de la base source, xms celle de la base destination
        public void transfer(String source, String target) throws DataControllerException {
            String sqlRequest, insertRequest;
            // <bveColumnName, xmsColumnName>
            Map<String, String> mapping = new HashMap<String, String>();
            // <columnName, columnDataType>
            Map<String, Integer> xmsColumnTypes = new HashMap<String, Integer>();
            // <columnName, columnNumber>
            Map<String, Integer> xmsColumnOrder = new HashMap<String, Integer>();
     
            PreparedStatement ps = null;
            int statementsCount = 0;
            int totalCount = 0;
            int batchNumber = 1;
            ResultSet rs = null;
            try {
                // loading mapping
                mapping = getColumsMapping(xms, source, target);
                // loading xms columns types
                xmsColumnTypes = getColumnTypes(target, xms);
                // loading columns order
                xmsColumnOrder = getColumnOrder(mapping);
                // building select * from source on bve database
                sqlRequest = createSelectRequest(source, mapping);
                // building insert into target on xms database
                insertRequest = createInsertSql(target, mapping);
     
    	    // insertStatement est un PreparedStatement chargé avec une requete 'insert into foobar(a,b,c...) values (?,?,?...)'
                insertStatement = xms.prepareStatement(insertRequest);
    	    // ps un PreparedStatement
                ps = bve.prepareStatement(sqlRequest, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
    	    // rs est un énorme ResultSet de 131 000 lignes de 50 colonnes resultant d'un 'select * from ...'
                rs = ps.executeQuery();
                rs.beforeFirst();
                long lastExec = System.currentTimeMillis();
                long now;
                while (rs.next()) {
                    // Cette méthode prend les valeurs de la ligne courante pour binder les ? 
    		// du PreparedStatement insertStatement avec les valeurs de rs. Ensuite j'ajoute au batch.
                    addInsertToBatch(rs, xmsColumnTypes, mapping, xmsColumnOrder);
                    statementsCount++;
                    totalCount++;
    		// tous les MAX_BATCH_SIZE (100), j'execute mon batch et je commite.
                    if (statementsCount == MAX_BATCH_SIZE) {
                        insertStatement.executeBatch();
                        xms.commit();
                        insertStatement.clearBatch();
                        //insertStatement = null;
                        //System.gc();
                        //insertStatement = xms.prepareStatement(insertRequest);
                        now = System.currentTimeMillis();
                        log.finest("Executed and commited " + MAX_BATCH_SIZE + " records batch number " + batchNumber + " in " + (now - lastExec) + "ms");
                        lastExec = now;
                        batchNumber++;
                        statementsCount = 0;
                    }
                }
                // some statements left for last batch ?
                if (statementsCount > 0) {
                    insertStatement.executeBatch();
                }
                log.fine(totalCount + " rows transfered");
            } catch blabla...
        }
     
     
        public void addInsertToBatch(ResultSet rs, Map<String, Integer> columnTypes, Map<String, String> mapping, Map<String, Integer> columnOrder)
                throws DataControllerException {
    	// peut etre une mauvaise idée de les déclarer ici 
    	// alors que cette methode est appelée 130 000 fois ?
            String xmsColumn;
            String bveColumn;
            int index;
            int type;
            try {
    	// Cette map sert a connaitre la correspondance entre nom de colonne du ResultSet et nom de colonne à insérer
                Iterator<Map.Entry<String, String>> it = mapping.entrySet().iterator();
                while (it.hasNext()) {
                    Map.Entry<String, String> pairs = (Map.Entry<String, String>) it.next();
                    xmsColumn = pairs.getValue();
                    bveColumn = pairs.getKey();
    		// columnTypes sert a connaitre le type de la colonne à inserer
                    type = columnTypes.get(xmsColumn);
    		// columnOrder sert a connaitre l'index de la colonne dans le PreparedStatement (à quel '?' la colonne correspond)
                    index = columnOrder.get(xmsColumn);
    		// Si la donnee est nulle je set un null
                    if (rs.getObject(bveColumn) == null) {
                        insertStatement.setNull(index, type);
                    } else {
    			// sinon selon le type je fais un setXxx
                        switch (type) {
                            case Types.BIGINT:
                            case Types.INTEGER:
                            case Types.SMALLINT:
                            case Types.TINYINT:
                                insertStatement.setInt(index, new Integer(rs.getInt(bveColumn)));
                                break;
                            case Types.BOOLEAN:
                                insertStatement.setBoolean(index, new Boolean(rs.getBoolean(bveColumn)));
                                break;
                            case Types.DECIMAL:
                                insertStatement.setBigDecimal(index, rs.getBigDecimal(bveColumn));
                                break;
                            case Types.DOUBLE:
                                insertStatement.setDouble(index, new Double(rs.getDouble(bveColumn)));
                                break;
                            case Types.FLOAT:
                                insertStatement.setFloat(index, new Float(rs.getFloat(bveColumn)));
                                break;
                            case Types.TIMESTAMP:
                                insertStatement.setTimestamp(index, rs.getTimestamp(bveColumn));
                                break;
                            case Types.TIME:
                            case Types.DATE:
                                insertStatement.setDate(index, rs.getDate(bveColumn));
                                break;
                            default:
                                insertStatement.setString(index, new String(rs.getString(bveColumn)));
                                break;
                        }
                    }
                }
    	// J'ajoute mon 'insert into ...' tout prêt au batch
                insertStatement.addBatch();
            } catch blabla...
        }

  2. #2
    Membre confirmé
    Inscrit en
    Juillet 2006
    Messages
    113
    Détails du profil
    Informations forums :
    Inscription : Juillet 2006
    Messages : 113
    Par défaut SQL Loader
    A mon avis 131 000 lignes de 50 colonnes ça va prendre beaucoup de temps pour être copié par JDBC. Il vaut mieux si tu fait un extraction sur un fichier (CVS par exemple) et chargé le fichier dans la nouvelle table on utilisant SQL Loader. Si la table cible contient des indexes des triggers etc.…, il vaut mieux copier le fichier CSV avec SQL Loader dans une table temporaire avant d’updaté la table cible.

  3. #3
    Modérateur
    Avatar de OButterlin
    Homme Profil pro
    Inscrit en
    Novembre 2006
    Messages
    7 313
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 7 313
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par SuperFoieGras Voir le message
    // ps un PreparedStatement
    ps = bve.prepareStatement(sqlRequest, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
    Je pense que ton problème vient de là.
    Comme le resultSet est "scrollable", il doit conserver les données lues.
    Essaye un simple
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    ps = bve.prepareStatement(sqlRequest);
    ResultSet rs = ps.executeQuery();
    while ( rs.next() )
    {
       ...
    }
    (sans le beforFirst() )
    N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  4. #4
    Membre expérimenté Avatar de aperrin
    Profil pro
    Inscrit en
    Octobre 2005
    Messages
    221
    Détails du profil
    Informations personnelles :
    Âge : 53
    Localisation : France

    Informations forums :
    Inscription : Octobre 2005
    Messages : 221
    Par défaut
    Bonjour,
    Essaie TYPE_FORWARD_ONLY à la place de ResultSet.TYPE_SCROLL_INSENSITIVE.
    Sinon essaie de voir en debug qu'elle est l'objet qui prend de plus en plus de place en mémoire cela te donnera des pistes.
    Je pencherais pour le resultset.

  5. #5
    Membre confirmé
    Inscrit en
    Juillet 2006
    Messages
    113
    Détails du profil
    Informations forums :
    Inscription : Juillet 2006
    Messages : 113
    Par défaut
    Citation Envoyé par aperrin Voir le message
    Sinon essaie de voir en debug qu'elle est l'objet qui prend de plus en plus de place en mémoire cela te donnera des pistes.
    Je pencherais pour le resultset.
    Comment faire pour savoir la place prise en mémoire par chaque objet ? Cela est il possible dans le mode debug d’eclipse ?

  6. #6
    Membre régulier
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    8
    Détails du profil
    Informations personnelles :
    Âge : 42
    Localisation : Suisse

    Informations forums :
    Inscription : Septembre 2008
    Messages : 8
    Par défaut
    Merci beaucoup pour toutes ces indications.
    Je suis en train de les tester, mais comme tout ceci est atrocement long je ne pourrai pas donner de conclusion avant un petit moment.

    Dapres mes recherches, il existe un moyen de décortiquer le heap avec Java 1.6, mais je suis obligé de rester en 1.5.

    Aperrin, est-il possible de connaitre la taille en mémoire de mes objets avec Eclipse version Europa, comme le demande si judicieusement Freakfm ?

    Je vous tiens au courant, et encore merci.

    SuperFoieGras

  7. #7
    Membre expérimenté Avatar de aperrin
    Profil pro
    Inscrit en
    Octobre 2005
    Messages
    221
    Détails du profil
    Informations personnelles :
    Âge : 53
    Localisation : France

    Informations forums :
    Inscription : Octobre 2005
    Messages : 221
    Par défaut
    Pour la taille d'un objet en mémoire
    http://www.javaworld.com/javaworld/j...avatip130.html

  8. #8
    Membre régulier
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    8
    Détails du profil
    Informations personnelles :
    Âge : 42
    Localisation : Suisse

    Informations forums :
    Inscription : Septembre 2008
    Messages : 8
    Par défaut
    La solution avec le mode TYPE_FORWARD_ONLY a l'air de fonctionner, la mémoire fluctue autour du même nombre et j'ai enfin passé le cap de la fatidique ligne 29 100. Je pense que la solution sans option du tout comme l'a proposé OButterlin marche aussi, je vais tester plus tard. Je n'ai pas encore processé toutes les lignes donc ca peut encore planter

    Par contre je n'arrive toujours pas a garder un temps constant d'execution. Au fur et a mesure les batchs de 100 mettent de plus en plus de temps à s'executer, passant d'environ 1,8s pour les premiers à 5,0s pour le batch 350 (35 000eme ligne).

    Est-ce la base qui flanche ou mon objet de type PreparedStatement qui arrive pas a suivre la cadence ?

    En tout cas merci, au moins maintenant ca marche, même si ca met trois plombes.

  9. #9
    Membre expérimenté Avatar de aperrin
    Profil pro
    Inscrit en
    Octobre 2005
    Messages
    221
    Détails du profil
    Informations personnelles :
    Âge : 53
    Localisation : France

    Informations forums :
    Inscription : Octobre 2005
    Messages : 221
    Par défaut
    En général ce type de comportement est symptomatique d'une fuite mémoire...

  10. #10
    Expert éminent
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par SuperFoieGras Voir le message
    Est-ce la base qui flanche ou mon objet de type PreparedStatement qui arrive pas a suivre la cadence ?
    Citation Envoyé par aperrin Voir le message
    En général ce type de comportement est symptomatique d'une fuite mémoire...
    +1

    Je ne vois aucune libération de ressources !!!!
    Tous les éléments de JDBC (Connection, Statement, ResultSet) doivent être explicitement fermés, de préférence via un try/finally...

    Grosso-modo tu as ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
     
    	// ...
     
    	// ps un PreparedStatement
    	ps = bve.prepareStatement(sqlRequest);
    	// rs est un énorme ResultSet de 131 000 lignes de 50 colonnes resultant d'un 'select * from ...'
    	rs = ps.executeQuery();
     
    	while (rs.next()) {
    		// ...
    	}
     
    	// ...
     
    } catch (SQLException e) {
    	// ...
    }
    Déja je ne vois pas trop l'intérêt de déclarer les ResultSet/Statement tout en haut de la méthode : il serait plus simple et plus efficace de les déclarer à leurs initialisation !

    Il serait donc préférable d'avoir quelque chose de ce genre là :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    try {
     
    	// ...
     
    	// ps un PreparedStatement
    	PreparedStatement ps = bve.prepareStatement(sqlRequest);
    	try {
    		// rs est un énorme ResultSet de 131 000 lignes de 50 colonnes resultant d'un 'select * from ...'
    		ResultSet rs = ps.executeQuery();
    		try {
    			while (rs.next()) {
    				// ...
    			}
    		} finally {
    			rs.close();
    		}
    	} finally {
    		ps.close();
    	}
     
    	// ...
     
    } catch (SQLException e) {
    	// ...
    }
    Comment libérer proprement les ressources (ou comment utiliser proprement les bloc try/finally) ?


    a++

  11. #11
    Modérateur
    Avatar de OButterlin
    Homme Profil pro
    Inscrit en
    Novembre 2006
    Messages
    7 313
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 7 313
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par adiGuba Voir le message
    Je ne vois aucune libération de ressources !!!!
    Tous les éléments de JDBC (Connection, Statement, ResultSet) doivent être explicitement fermés, de préférence via un try/finally...
    Question à ce sujet :
    Lorsqu'on ferme une connexion, toutes les ressources sont libérées
    close()
    Releases this Connection object's database and JDBC resources immediately instead of waiting for them to be automatically released.
    Y a-t-il une nécessité de fermer le ResultSet et le Statement (ou PreparedStatement) également ?
    N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  12. #12
    Expert éminent
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par OButterlin Voir le message
    Y a-t-il une nécessité de fermer le ResultSet et le Statement (ou PreparedStatement) également ?
    Je dirais oui : pour libérer les ressources au plus tôt !

    Je n'ai pas regardé le code en profondeur (j'ai juste vu qu'il n'y avait pas de close()), mais j'ai vu beaucoup de code avec une connection JDBC et plusieurs statement/resultset qui n'était pas libéré (sauf lors du close() final sur la connection).

    Le problème c'est que cela génère des ressources supplémentaires qui resteront actives et qui peuvent plomber les performances (par exemple : un curseur qui reste inutilement ouvert sur la BD...).


    Bref pour moi il est préférable de libérer tout ceci au plus tôt !

    a++

  13. #13
    Membre régulier
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    8
    Détails du profil
    Informations personnelles :
    Âge : 42
    Localisation : Suisse

    Informations forums :
    Inscription : Septembre 2008
    Messages : 8
    Par défaut
    AdiGuba,
    J'ai bien un bloc finally avec tous les close() nécessaires. Je ne les avais pas postés pour ne pas encombrer. De toute façon mon OutOfMemoryError survenait pendant la boucle, les rs.close et ps.close n'y auraient rien fait.

    OButterlin,
    Normalement tu n'as pas besoin de fermer PreparedStatement et ResultSet si tu fermes la Connection, mais en general il vaut mieux la mettre dans un pool, et donc juste fermer ps et rs. Attention, dans la même logique si tu fermes le ps ton ResultSet sera fermé aussi.

    Ma mémoire reste a peu pres stable, je ne pense pas que le problème vienne de là. Le goulot d'étranglement est le executeBatch(). Après avoir discuté avec des collègues je pense que le problème vient de la base, probablement d'un trigger qui se déclenche à chaque insert.

    Je vous tiens au courant

  14. #14
    Expert éminent
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par SuperFoieGras Voir le message
    J'ai bien un bloc finally avec tous les close() nécessaires. Je ne les avais pas postés pour ne pas encombrer.
    On pourrait voir le code complet ?
    Car une fuite mémoire peut parfois être dû à de petits détails, qui pourrait être malencontreusement caché par un code tronqué...


    a++

  15. #15
    Membre régulier
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    8
    Détails du profil
    Informations personnelles :
    Âge : 42
    Localisation : Suisse

    Informations forums :
    Inscription : Septembre 2008
    Messages : 8
    Par défaut
    AdiGuba,

    j'ai seulement tronqué le bloc catch/finally. Le problème est résolu.

    CONCLUSION :

    La solution de OButterlin
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    ps = bve.prepareStatement(sqlRequest);
    ainsi que celle de Aperrin
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    ps = bve.prepareStatement(sqlRequest, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
    fonctionnent.

    La lenteur croissante de l'execution des batch etait due a un trigger dans la base. En le supprimant, les batch de 100 sont tous passés en 175ms en moyenne .

    Merci beaucoup à vous tous qui avez participé à cette discution, c'était la première fois que je faisais appel à un forum pour un problème et je suis agréablement surpris par la rapidité et la pertinence des réponses .

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 3
    Dernier message: 24/04/2008, 12h09
  2. java.lang.OutOfMemoryError: Java heap space
    Par othmanbenhalima dans le forum Général Java
    Réponses: 12
    Dernier message: 08/01/2008, 17h46
  3. [Findbugs] [Maven] java.lang.OutOfMemoryError: Java heap space
    Par albaille dans le forum Qualimétrie
    Réponses: 1
    Dernier message: 10/04/2007, 15h17
  4. Réponses: 4
    Dernier message: 18/09/2006, 10h02
  5. Eclipse erreur : java.lang.OutOfMemoryError: Java heap space
    Par sderecourt dans le forum Eclipse Java
    Réponses: 1
    Dernier message: 14/04/2006, 11h28

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo