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

JDBC Java Discussion :

OutOfMemoryError sur insertions MySQL


Sujet :

JDBC Java

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Inscrit en
    Octobre 2007
    Messages
    14
    Détails du profil
    Informations forums :
    Inscription : Octobre 2007
    Messages : 14
    Par défaut OutOfMemoryError sur insertions MySQL
    Bonjour à tous,

    J'ai un soucis de fuite mémoire avec le driver JDBC MySQL.
    Dans une boucle, je fais environ 150 000 INSERT.
    Avant d'arriver à la fin, l'appli sort sur un OutOfMemoryError. J'ai inspecté mon code, je ne vois pas de fuite mémoire. Après analyse de la mémoire heap, il semble que ce soit le driver MySQL qui consomme toute la mémoire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    One instance of "com.mysql.jdbc.Connection" loaded by
    "sun.misc.Launcher$AppClassLoader @ 0x2xxxxxxxx8" occupies
    759 916 912 (99,79%) bytes. The memory is accumulated in one
    instance of "java.util.HashMap$Entry[]" loaded by "<system class loader>".
    A priori, pas de fuite mémoire non plus dans ma fonction d'insertion. J'ai essayé pas mal de chose : fermer mon preparedStatement et mon resultSet, forcer le passage du GC, rien n'y fait. En désespoir de cause, j'ai tenté une déconnexion / reconnexion, et là, ca fonctionne.

    Qu'est-ce que j'ai bien pu oublier ? Quelqu'un a t-il déjà été confronté à ce problème, quelle solution (je trouve la déconnexion / reconnexion pas élégante du tout) ?

    Merci

  2. #2
    Expert éminent
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 482
    Par défaut
    code?

  3. #3
    Membre averti
    Inscrit en
    Octobre 2007
    Messages
    14
    Détails du profil
    Informations forums :
    Inscription : Octobre 2007
    Messages : 14
    Par défaut
    Voilà le code de la fonction d'insertion dans la BDD

    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
        public int insertAIPref(int idSync, String identifiantVersement, long idLotArcsys) throws ArcheckException
        {
            if (idSync < 1 ||
                identifiantVersement == null ||
                idLotArcsys < 1)
            {
                handleSQLException("<insertAIPref> Une ou plusieurs données sont nulles : idSync [" + idSync + "], identifiantVersement [" + identifiantVersement + "], idLotArcsys [" + idLotArcsys + "]");
            }
            int id = -1;
            try
            {
                // on insert la nouvelle definition
                logger.debug("<insertAIPref> insertion de la référence sur l'AIP d'identifiant Arcsys [" + idLotArcsys + "] dans le referentiel");
     
                sql = "INSERT INTO AIPref (id_sync, id_versement, id_lot_arcsys) VALUES (?,?,?)";
                preparedStatement = conn.prepareStatement(sql);
                compteur = 1;
                preparedStatement.setInt(compteur++, idSync);
                preparedStatement.setString(compteur++, identifiantVersement);
                preparedStatement.setLong(compteur++, idLotArcsys);
     
                preparedStatement.executeUpdate();
                rs = preparedStatement.getGeneratedKeys();
                // recupération de l'identifiant
                if (rs.next())
                {
                    id = rs.getInt(1);
                }
                else
                {
                    handleSQLException("<insertAIPref> erreur lors du chargement de l'identifiant de l'AIPref [" + idLotArcsys + "]");
                }
            }
            catch (SQLException exc)
            {
                   if (exc.getErrorCode() == 1062)
                   {
                    logger.warn("Le lot d'identifiant Arcsys [" + idLotArcsys + "] est déjà présent dans le référentiel.");
                    String sql = "SELECT id FROM AIPref WHERE id_lot_arcsys=?";
                    try
                    {
                        preparedStatement = conn.prepareStatement(sql);
                        compteur = 1;
                        preparedStatement.setLong(compteur++, idLotArcsys);
                        rs = preparedStatement.executeQuery();
                        if (rs.next())
                        {
                            // l'extension existe déjà, on récupere l'identifiant
                            id = rs.getInt("id");
                        }
                    }
                    catch (SQLException e)
                    {
                        handleSQLException("<insertAIPref> Erreur SQL [" + exc.getMessage() + "]", exc);
                    }
     
                   }
                else
                    handleSQLException("<insertAIPref> Erreur SQL [" + exc.getMessage() + "]", exc);
            }
            return id;
        }
    Et régulièrement (toutes les 1000 insertions), je commit, j'appelle cette fonction, puis je lance le GC.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
        public void releaseStatements() throws SQLException
        {
            preparedStatement.close();
            rs.close();
            disconnect();
            connect();
        }
    J'aurai aimé éviter le disconnect() / connect() que je ne trouve pas élégant du tout !...

  4. #4
    Expert éminent
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 482
    Par défaut
    dans ton insertAIPref, tu oublie de fermer tes perparedStatement et tes resultset, cve qui consomme de la mémoire. Un code correct doit être basé sur ce shéma.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    preparedStatement = conn.prepareStatement(sql);
    try{
        //....
    } finally {
        preparedStatement.close();
    }
    même chose avec les resultset
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    rs = preparedStatement.xxxxx();
    try{
       //.....
    } finally {
        rs.close();
    }

  5. #5
    Membre averti
    Inscrit en
    Octobre 2007
    Messages
    14
    Détails du profil
    Informations forums :
    Inscription : Octobre 2007
    Messages : 14
    Par défaut
    Bon... J'ai ajouté un finally à chaque try, dans lequel je ferme mon ResultSet et mon PreparedStatement. J'ai relancé, et au bout d'un moment (un peu plus tard que d'habitude il me semble), il me sort un beau OutOfMemoryError...

    D'ailleurs, ca ne métonne qu'à moitié : je faisais manuellement (toutes les 1000 requètes) un close() sur mon rs et preparedStatement, et en dehors de ces fermetures explicites, j'écrasais les références (donc passage du GC et libération mémoire).

    Ca semble indiquer une gestion sous-jacente de la mémoire par le driver un peu faiblarde : une plage mémoire déréférencée dans java, et libérée par le GC resterait affectée dans le driver ?

    D'autres idées ?

  6. #6
    Expert éminent
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 482
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 482
    Par défaut
    Citation Envoyé par ben_cines Voir le message
    Bon... J'ai ajouté un finally à chaque try, dans lequel je ferme mon ResultSet et mon PreparedStatement. J'ai relancé, et au bout d'un moment (un peu plus tard que d'habitude il me semble), il me sort un beau OutOfMemoryError...
    Tu peux nous donner le code corriger, ainsi que le code appelant

    en dehors de ces fermetures explicites, j'écrasais les références (donc passage du GC et libération mémoire).
    Si tu ne fais pas un close, ça ne sera libéré que lorsque tu fera un close de la connection. Tu n'est pas le seul à garder des référence sur ces objet, la connexion en garde aussi! D'ou l'intérêt du close()

  7. #7
    Expert confirmé
    Avatar de sinok
    Profil pro
    Inscrit en
    Août 2004
    Messages
    8 765
    Détails du profil
    Informations personnelles :
    Âge : 45
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Août 2004
    Messages : 8 765
    Par défaut
    Ca semble indiquer une gestion sous-jacente de la mémoire par le driver un peu faiblarde : une plage mémoire déréférencée dans java, et libérée par le GC resterait affectée dans le driver ?
    Le driver MySQL étant écrit en pur java et étant soumis aux limites de ta JVM, il ne peut en aucun cas être soumis à des limite différentes que celle de ta JVM niveau mémoire.

    Le premier réflexe à avoir quand tu as une fuite mémoire tele que celle ci est de sortir un profiler et de le brancher sur ta JVM.

    En l'occurence VisualVM qui est fournit avec les JDK6 récents (exécutable jvisualvm dans le dossier bin du répertoire d'installation du JDK) fera tout à fait l'affaire pour monitorer la mémoire de ton application, pour cibler où se trouve la fuite.

    Je doute franchement que le driver soit responsable de la fuite, en effet c'est un composant utilisé plus que régulièrement dans des environnements de production, cela m'étonnerait quelques peu.

  8. #8
    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
    Salut,

    Citation Envoyé par ben_cines Voir le message
    D'ailleurs, ca ne métonne qu'à moitié : je faisais manuellement (toutes les 1000 requètes) un close() sur mon rs et preparedStatement, et en dehors de ces fermetures explicites, j'écrasais les références (donc passage du GC et libération mémoire).
    Ce que je vois surtout, c'est que ton PreparedStatement et ton ResultSet ne sont pas déclarée dans le corps de la méthode. Je suppose donc qu'il s'agit d'attribut d'instance de ta classe.

    Tu utilises des attributs d'instances pour stocker des variables locales

    Et lorsque tu dis que tu fais un close() toutes les 1000 requêtes... tu te contentes en fait de fermer 1 requête sur 1000 !!!


    a++

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

Discussions similaires

  1. Datetime + Mysql problème sur insertion
    Par hirochirak dans le forum ASP.NET
    Réponses: 12
    Dernier message: 08/12/2008, 17h34
  2. [MySQL] memory limit sur insertion dans MySQL
    Par bru2336 dans le forum PHP & Base de données
    Réponses: 4
    Dernier message: 13/11/2007, 08h32
  3. Problème sur requête insert mysql
    Par kcizth dans le forum Requêtes
    Réponses: 5
    Dernier message: 04/02/2006, 18h37
  4. Réponses: 4
    Dernier message: 28/09/2002, 00h00
  5. Réponses: 4
    Dernier message: 28/09/2002, 00h00

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