40

I created a web page that makes an Ajax call every second. In Internet Explorer 7, it leaks memory badly (20 MB in about 15 minutes).

The program is very simple. It just runs a JavaScript function that makes an Ajax call. The server returns an empty string, and the JavaScript code does nothing with it. I use setTimeout to run the function every second, and I'm using Drip to watch the thing.

Here is the 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>

How to plug this leak? I have a real application that updates a large table this way, but left unattended it will eat up gigabytes of memory.

Edit: okay, so after some good suggestions, I modified the code to:

<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>

It didn't seem to make any difference, though. I'm not doing anything with the DOM, and if I comment out the Ajax call, the memory leak stops. So it looks like the leak is entirely in the Ajax call. Does jQuery Ajax inherently create some sort of circular reference, and if so, how can I free it? By the way, it doesn't leak in Firefox.

Someone suggested running the test in another VM and see if the results are the same. Rather than setting up another VM, I found a laptop that was running XP Home with Internet Explorer 8. It exhibits the same problem.

I tried some older versions of jQuery and got better results, but the problem didn't go away entirely until I abandoned Ajax in jQuery and went with more traditional (and ugly) Ajax.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Thomas Lane
  • 1,111
  • 1
  • 9
  • 9
  • Also very curious about the answer--I have ideas but want to know the results. – Plynx Mar 11 '10 at 22:14
  • Good question, I don't know the answer but you might consider having the setTimeout in the success function so you don't end up overloading the server if you start to make requests that takes time. In case you didn't get the whole response from the server before you send the next request you will open a new connection. – MyGGaN Mar 11 '10 at 22:20
  • Thanks for the suggestion about moving setTimeout. I tried it, but it didn't help. – Thomas Lane Mar 11 '10 at 22:29
  • Are you doing anything with the results that might be causing the problem? – PetersenDidIt Mar 11 '10 at 22:34
  • I'm not doing anything with the results. Except for the url, I'm running the program as listed above. – Thomas Lane Mar 11 '10 at 22:42

8 Answers8

19

Here's a link to the bug over on jQuery, along with this as a suggested fix for 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: This is officially fixed in jQuery 1.4.4, so your best bet is to just upgrade now.

Ryley
  • 21,046
  • 2
  • 67
  • 81
8

The problem appears to be with jQuery 1.4 in Internet Explorer, and to a lesser extent, versions 1.2 and 1.3.

1.4.0, 1.4.1, and 1.4.2 all exhibited the severe memory leak.

1.2.3, 1.2.6, 1.3.0, 1.3.1, and 1.3.2 all exhibited a much smaller leak (about 100 KB after 10 minutes).

I also tried a version of my program that calls Ajax in a more traditional way:

<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>

This got rid of the leak entirely.

So it looks like I'll have to do my repeating Ajax calls the ugly old way until the jQuery folks iron out this problem.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Thomas Lane
  • 1,111
  • 1
  • 9
  • 9
  • 1
    Yeah, if you want to keep doing things the jQuery way, use the patch in my answer... Actually having to manage your XHR stuff directly sucks! – Ryley May 28 '10 at 18:18
  • funnily enough, we still appear to have an issue in the current JQuery, 1.6.2 at time of posting. Multiple calls to .getJSON consumes more and more memory until you kill the process, even if you do nothing to content. http://stackoverflow.com/questions/6752335/memory-leak-when-pulling-json-from-web – Russ Clarke Jul 19 '11 at 20:31
7

I encountered the same issue and had been stumped all morning ... until a few moments ago. The problem is a circular reference that is created when you set the onreadystatechange handler, that IE isn't smart enough to break. The solution, therefore, is to break it explicitly. However, obviously you can't do it from within the handler itself (though it would be convenient if you could!).

The magic statement:

delete request['onreadystatechange'];

You need to keep a record of each XMLHttpRequest object for which you set onreadystatechange. Then, at some point after readyState goes to 4, do your magic on the object. If you are already performing a repeated AJAX poll, the logical place to check for requests to clean up would be in the same polling loop. I defined a simple RequestTracker object to manage my requests.

This worked for me; I verified that it solved the leak. Here's one link in particular that led the way (I would post more, but StackOverflow isn't letting me):

Haw-Bin
  • 416
  • 3
  • 8
5

eval() will eat up memory for sure (eval happens when passing a string to setTimeout to evaluate), don't use it in testing:

setTimeout('testJunk()',1000);

should be:

setTimeout(testJunk, 1000);

Also a better use overall would be setInterval() for a repeated operation like you want, try this:

setInterval(testJunk, 1000);
Nick Craver
  • 623,446
  • 136
  • 1,297
  • 1,155
  • Thanks for the suggestion. I tried it. If it made a difference, it was small. Memory is still going up. – Thomas Lane Mar 11 '10 at 22:30
  • @Thomas - Is that the entire page in your question, or just an abbreviated version and more is happening in the full version? – Nick Craver Mar 11 '10 at 22:34
  • I'm running the program as listed above, except that I've mangled the url for the listing. – Thomas Lane Mar 11 '10 at 22:44
  • @Thomas - I've left your sample running for the past half hour in 2 different IE7 VMs...memory usage climbs a few megs then looks like garbage collection kicks in and knocks it back down. I would test on another machine, best to get a third sample. – Nick Craver Mar 11 '10 at 23:07
  • Nick - Interesting. In my case it doesn't appear to be a lack of garbage collection. I added a call to "CollectGarbage" in my testJunk function and it didn't help. I ran it on a different VM running IE6 and have no memory leaks there. I'm going to try it on a system with IE8 and see what happens. – Thomas Lane Mar 12 '10 at 15:33
0

if you're using the setinterval in javascript and don't clear it properly, the timer can start multiple times, causing a stack of calls.

try something like

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

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

One problem with your code is that if your ajax request start to take a while you will start flooding the browser and server with ajax request you really should wait till the browser gets a return from the server before kicking off the next one.

function testJunk() {
    $.ajax({ url: 'http://xxxxxxxxxxxxxx/test', // The url returns an empty string
        dataType: 'html',
        complete: function(data){
            setTimeout(testJunk,1000);
        }
    });
}
testJunk();
PetersenDidIt
  • 25,562
  • 3
  • 67
  • 72
0

I've seen this, and I don't believe it's a memory leak per-se. It's just that the Ajax request returns with no data because of caching.

Add deliberate cache control, as in:

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

It's one of those things where stuff falls down the cracks, that is, does a specific thing with caching and XMLHttpRequest and jQuery doesn't use cache off by default.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
nic ferrier
  • 1,573
  • 13
  • 14
0

Just encountered this myself. I thought it was something to do with the UI library initially, but then it disappeared after I swapped in jQuery 1.5. for the version 1.4.2 that I was using. (1.4.4 didn't seem to fix the issue).

James Wiseman
  • 29,946
  • 17
  • 95
  • 158