web-dev-qa-db-fra.com

Un simple appel jQuery Ajax perd de la mémoire dans Internet Explorer

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

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 ne fait rien avec elle. J'utilise setTimeout pour exécuter la fonction toutes les secondes et j'utilise Drip pour regarder la chose.

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 boucher cette fuite? J'ai une vraie application qui met à jour une grande table de cette façon, mais laissée sans surveillance, elle va consommer des gigaoctets de mémoire.

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

<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 ne semblait pourtant pas faire de différence. Je ne fais rien avec le DOM, et si je commente l'appel Ajax, la fuite de mémoire cesse. Donc, il semble que la fuite soit entièrement dans l'appel Ajax. JQuery Ajax crée-t-il de manière inhérente une sorte de référence circulaire, et si oui, comment puis-je le libérer? À propos, il ne fuit pas dans Firefox.

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

J’ai essayé d’anciennes versions de jQuery et obtenu de meilleurs résultats, mais le problème ne s’est pas entièrement dissipé jusqu’à ce que j’ai abandonné Ajax dans jQuery et que je suis parti avec un Ajax plus traditionnel (et moche).

40
Thomas Lane

Le problème semble être lié à jQuery 1.4 dans Internet Explorer et, dans une moindre mesure, aux versions 1.2 et 1.3.

1.4.0, 1.4.1 et 1.4.2 ont tous présenté la fuite de mémoire grave.

Les fuites 1.2.3, 1.2.6, 1.3.0, 1.3.1 et 1.3.2 ont toutes présenté une fuite beaucoup plus petite (environ 100 Ko après 10 minutes).

J'ai aussi 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 s'est entièrement débarrassé de la fuite.

Il semble donc que je devrai répéter mon appel à Ajax, comme avant, jusqu'à ce que les gens de jQuery résolvent ce problème.

8
Thomas Lane

Voici un lien au bogue sur jQuery, ainsi qu'un correctif suggéré 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: Ceci est officiellement corrigé dans jQuery 1.4.4, votre meilleur pari est donc simplement de mettre à jour maintenant.

19
Ryley

J'ai rencontré le même problème et j'avais été perplexe toute la matinée ... jusqu'à il y a quelques instants. Le problème est une référence circulaire créée lorsque vous définissez le gestionnaire onreadystatechange, que IE n'est pas assez intelligent pour décomposer. La solution consiste donc à le casser explicitement. Cependant, évidemment, vous ne pouvez pas le faire depuis le gestionnaire lui-même (bien que ce serait pratique si vous le pouviez!).

La déclaration magique:

delete request['onreadystatechange'];

Vous devez conserver un enregistrement de chaque objet XMLHttpRequest pour lequel vous définissez onreadystatechange. Puis, à un moment donné après que readyState passe à 4, faites votre magie sur l'objet. Si vous effectuez déjà une interrogation répétée AJAX, l'emplacement logique dans lequel rechercher les demandes de nettoyage se trouverait dans la même boucle d'interrogation. J'ai défini un objet RequestTracker simple pour gérer mes demandes.

Cela a fonctionné pour moi. J'ai vérifié que cela résolvait la fuite. Voici un lien en particulier qui a ouvert la voie (je posterais plus, mais StackOverflow ne me laisse pas faire):

7
Haw-Bin

eval() va certainement occuper de la mémoire (une évaluation survient lors du passage d'une chaîne à setTimeout pour une évaluation), ne l'utilisez pas dans les tests:

setTimeout('testJunk()',1000);

devrait être:

setTimeout(testJunk, 1000);

Également, une meilleure utilisation globale serait setInterval() pour une opération répétée comme vous le souhaitez, essayez ceci:

setInterval(testJunk, 1000);
5
Nick Craver

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 avec une requête ajax. Vous devez 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();
0
PetersenDidIt

si vous utilisez setinterval en javascript et que vous ne l'effacez pas correctement, le minuteur peut démarrer plusieurs fois, générant une pile d'appels.

essayez quelque chose comme

var myVar = setInterval(function() { clear() }, 5000);

function clear() { 
    clearInterval(myVar); 
    GetData("ServiceLibrary","GetCalls",sdata,Complete);
};
0
sully

Juste rencontré moi-même. Je pensais que cela avait quelque chose à voir avec la bibliothèque d'interface utilisateur au départ, mais elle a ensuite disparu après que j'ai échangé jQuery 1.5. pour la version 1.4.2 que j'utilisais. (1.4.4 n'a pas semblé résoudre le problème).

0
James Wiseman

J'ai vu cela et je ne crois pas que ce soit une fuite de mémoire en soi. C'est juste que la demande Ajax retourne sans données à cause de la mise en cache.

Ajouter un contrôle de cache délibéré, comme dans: 

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

C’est l’une de ces choses où les choses tombent entre les mailles du filet, c’est-à-dire une chose spécifique avec la mise en cache et XMLHttpRequest et jQuery n’utilise pas la mémoire cache par défaut.

0
nic ferrier