40 votes

Un simple appel Ajax de jQuery entraîne une fuite de mémoire dans Internet Explorer

J'ai créé une page Web qui effectue un appel Ajax toutes les secondes. Dans Internet Explorer 7, elle perd beaucoup de mémoire (20 Mo en 15 minutes environ).

Le programme est très simple. Il exécute simplement une fonction JavaScript qui effectue un appel Ajax. Le serveur renvoie une chaîne vide, et le code JavaScript n'en fait rien. J'utilise setTimeout pour exécuter la fonction toutes les secondes, et j'utilise Goutte à goutte pour regarder le truc.

Voici la source :

<html>
  <head>
    <script type="text/javascript" src="http://www.google.com/jsapi"></script>
    <script type="text/javascript">
      google.load('jquery', '1.4.2');
      google.load('jqueryui', '1.7.2');
    </script>
    <script type="text/javascript">
      setTimeout('testJunk()',1000);
      function testJunk() {
        $.ajax({ url: 'http://xxxxxxxxxxxxxx/test', // The url returns an empty string
                 dataType: 'html',
                 success: function(data){}
               });
        setTimeout('testJunk()',1000)
      }
    </script>
  </head>
  <body>
    Why is memory usage going up?
  </body>
</html>

Comment colmater cette fuite ? J'ai une application réelle qui met à jour une grande table de cette façon, mais si elle n'est pas surveillée, elle consomme des gigaoctets de mémoire.

Editar : ok, donc après quelques bonnes suggestions, j'ai modifié le code pour :

<html>
  <head>
    <script type="text/javascript" src="http://www.google.com/jsapi"></script>
    <script type="text/javascript">
      google.load('jquery', '1.4.2');
      google.load('jqueryui', '1.7.2');
    </script>
    <script type="text/javascript">
      setTimeout(testJunk,1000);
      function testJunk() {
        $.ajax({ url: 'http://xxxxxxxxxxxxxx/test', // The url returns an empty string
                 dataType: 'html',
                 success: function(data){setTimeout(testJunk,1000)}
               });
      }
    </script>
  </head>
  <body>
    Why is memory usage going up?
  </body>
</html>

Cela n'a pas semblé faire de différence, cependant. Je ne fais rien avec le DOM, et si je commente l'appel Ajax, la fuite de mémoire s'arrête. Il semble donc que la fuite se situe entièrement dans l'appel Ajax. Est-ce que jQuery Ajax crée intrinsèquement une sorte de référence circulaire, et si oui, comment puis-je la libérer ? Au fait, il n'y a pas de fuite dans Firefox.

Quelqu'un a suggéré d'exécuter le test dans une autre VM et de voir si les résultats sont les mêmes. Plutôt que de mettre en place une autre VM, j'ai trouvé un ordinateur portable qui fonctionnait sous XP Home avec Internet Explorer 8. Il présente le même problème.

J'ai essayé des versions plus anciennes de jQuery et j'ai obtenu de meilleurs résultats, mais le problème n'a pas disparu entièrement jusqu'à ce que j'abandonne Ajax dans jQuery et que j'opte pour un Ajax plus traditionnel (et plus laid).

19voto

Ryley Points 11916

Voici un lien au bogue sur jQuery, ainsi que cette suggestion de correction pour jQuery 1.4.2 :

--- jquery-1.4.2.js     2010-04-08 12:10:20.000000000 -0700
+++ jquery-1.4.2.js.fixed       2010-04-08 12:10:38.000000000 -0700
@@ -5219,7 +5219,7 @@

                            // Stop memory leaks
                            if ( s.async ) {
-                                       xhr = null;
+                                       xhr.onreadystatechange = null; xhr.abort = null; xhr = null;
                            }
                    }
            };

NOTE : Ce problème a été officiellement corrigé dans la version 1.4.4 de jQuery. Il est donc préférable de procéder à la mise à jour dès maintenant.

8voto

Thomas Lane Points 566

Le problème semble concerner la version 1.4 de jQuery dans Internet Explorer et, dans une moindre mesure, les versions 1.2 et 1.3.

Les versions 1.4.0, 1.4.1 et 1.4.2 présentaient toutes la fuite de mémoire grave.

Les versions 1.2.3, 1.2.6, 1.3.0, 1.3.1 et 1.3.2 présentaient toutes une fuite beaucoup plus faible (environ 100 Ko après 10 minutes).

J'ai également essayé une version de mon programme qui appelle Ajax d'une manière plus traditionnelle :

<html>
  <head>
    <script language="javascript" type="text/javascript">
      function getHTTPObject() {
        var xmlhttp;
        /*@cc_on
        @if (@_jscript_version >= 5)
          try {
            xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
          } catch (e) {
            try {
              xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
            } catch (E) {
              xmlhttp = false;
            }
          }
        @else
        xmlhttp = false;
        @end @*/
        if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
          try {
            xmlhttp = new XMLHttpRequest();
            if (xmlhttp.overrideMimeType) {
              xmlhttp.overrideMimeType("text/xml"); 
            }
          } catch (e) {
            xmlhttp = false;
          }
        }
        return xmlhttp;
      }
      var ajaxObject = getHTTPObject();
      setTimeout(testJunk,1000);
      function testJunk() {
        ajaxObject.open('POST', 'http://XXXXXXXXXXXXXXX/delme2', true);
        ajaxObject.onreadystatechange = handleAjaxResponse;
        ajaxObject.send(null);
      }
      function handleAjaxResponse() {
        if (ajaxObject.readyState==4) {
          setTimeout(testJunk,1000);
        }
      }
    </script>
  </head>
  <body>
    <div id="test">Why is memory usage going up?</div>
  </body>
</html>

Cela a permis d'éliminer complètement la fuite.

Il semble donc que je doive faire mes appels Ajax répétitifs de l'ancienne manière, jusqu'à ce que les gens de jQuery résolvent ce problème.

7voto

Haw-Bin Points 168

J'ai rencontré le même problème et je suis resté bloqué toute la matinée... jusqu'à il y a quelques instants. Le problème est une référence circulaire qui est créée lorsque vous définissez l'option onreadystatechange qu'IE n'est pas assez intelligent pour casser. La solution, par conséquent, est de le casser explicitement. Cependant, il est évident que vous ne pouvez pas le faire à partir du gestionnaire lui-même (bien que ce serait pratique si vous le pouviez !).

La déclaration magique :

delete request['onreadystatechange'];

Vous devez tenir un registre de chaque XMLHttpRequest pour lequel vous avez défini onreadystatechange . Puis, à un moment donné après readyState va à 4, fais ta magie sur l'objet. Si vous effectuez déjà un sondage AJAX répété, l'endroit logique pour vérifier les demandes à nettoyer serait dans la même boucle de sondage. J'ai défini un simple RequestTracker pour gérer mes demandes.

Cela a fonctionné pour moi ; j'ai vérifié que cela a résolu la fuite. Voici un lien en particulier qui m'a guidé (j'en posterais bien plus, mais StackOverflow ne me laisse pas faire) :

5voto

Nick Craver Points 313913

eval() consommera de la mémoire à coup sûr (l'évaluation se fait en passant une chaîne à setTimeout pour qu'elle soit évaluée), ne l'utilisez pas en test :

setTimeout('testJunk()',1000);

devrait être :

setTimeout(testJunk, 1000);

Une meilleure utilisation générale serait setInterval() pour une opération répétée comme vous le souhaitez, essayez ceci :

setInterval(testJunk, 1000);

0voto

PetersenDidIt Points 17498

Un problème avec votre code est que si votre requête ajax commence à prendre un certain temps, vous allez commencer à inonder le navigateur et le serveur de requêtes ajax. Vous devriez vraiment attendre que le navigateur reçoive un retour du serveur avant de lancer la suivante.

function testJunk() {
    $.ajax({ url: 'http://xxxxxxxxxxxxxx/test', // The url returns an empty string
        dataType: 'html',
        complete: function(data){
            setTimeout(testJunk,1000);
        }
    });
}
testJunk();

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X