Life of JavaScript objects & Memory Leaks

2019-03-19 03:58发布

问题:

I have researched quite a bit about this but mostly by piecing other questions together, which still leaves some doubt. In an app that does not refresh the browser page at any time and may live for quite a while (hours) without closing (assuming that refreshing a page or navigating to another would restart the js code), what's the best way to ensure objects are released and that there's no memory leak.

These are the specific scenarios I'm concerned about:

All of the code below is within a revealing module pattern.

mycode = function(){}()

variables within functions, I'm sure this one is collected by the GC just fine

function(){ var h = "ss";}

variables within the module, should g = null when it's no longer needed?

var g;
function(){ g = "dd";}

And lastly the life of a jqXHR: is it cleaned up after it returns? Should it be set to null in all cases as a precaution whether kept inside a function or module?

If doing this, is it x cleaned up by the GC after it returns?:

function(){
   var x = $.get();
   x.done = ...;
   x.fail = ...;
}

How about when doing this, will it also be cleaned up after x returns?:

var x;
function(){
   x = $.get();
   x.done = ...;
   x.fail = ...;
}

Lastly, is there a way to cleanup all variables and restart a module without restarting the browser?

回答1:

variables within functions, I'm sure this one is collected by the GC just fine

Yes.

variables within the module, should g = null when it's no longer needed?

Sure.

And lastly the life of a jqXHR: is it cleaned up after it returns? Should it be set to null in all cases as a precaution whether kept inside a function or module?

Various browsers have had bugs related to XHR that caused the onreadystatechange and anything it closed over to remain uncollectable unless the dev was careful to replace it with a dummy value (xhr.onreadystatechange = new Function('')) but I believe jQuery handles this for you.

Lastly, is there a way to cleanup all variables and restart a module without restarting the browser?

Global state associated with the page will take up browser memory until the page is evicted from the browser history stack. location.replace can help you here by letting you kill the current page and replace it with a new version of the same app without expanding the history stack.

Replace the current document with the one at the provided URL. The difference from the assign() method is that after using replace() the current page will not be saved in session history, meaning the user won't be able to use the Back button to navigate to it.

When you use the word "module", that is not a term that has a well-defined meaning to the browser or its JavaScript interpreter so there is no way to evict a module and only a module from memory. There are several things that you have to worry about that might keep things in memory:

  1. References to JavaScript objects that have been attached to DOM nodes and everything they close over -- event handlers are a very common example.
  2. Live setInterval and setTimeout callbacks and everything they close over.
  3. Properties of the global object and everything they close over.
  4. As you noted, properties of certain host objects like XHR instances, web worker callbacks, etc. and (you guessed it) everything they close over.

Any scheme that is going to unload a module and only a module would need to deal with all of these and figure out which of them are part of the module and which are not. That's a lot of different kinds of cleanup.



回答2:

Javascript is a garbage-collected language. It relies on the garbage collector to clean up unused memory. So essentially, you have to trust that the GC will do its job.

The GC will (eventually, not necessarily immediately) collect objects that are unreachable to you. If you have a reference to an object, then it is potentially still in use, and so the GC won't touch it.

If you have no reference to the object, directly or indirectly, then the GC knows that the object cannot possibly be used, and the object can be collected. So all you have to do, really, is make sure you reset any references to the object.

However, the GC makes no guarantees about when the object will be collected. And you shouldn't need to worry about that.



回答3:

Really the only leaks you should worry about are closures.

function foo(a){
    var b = 10 + a;
    return function(c){
        return b + c;
    }
}

var bar = foo(20);
var baz = bar(5);

The GC has no way to delete var b - it's out of scope. This is a big problem with IE, not so much with Mozilla and much less with Chrome.



回答4:

As a rule of thumb with any garbage-collected language (this applies to Java, .NET and JavaScript for example), what you want to do is make sure that there is no lingering reference to a block of memory that you want to have the GC clean up for you. When the GC looks at a block of memory and finds that there is still something in the program referencing it, then it will avoid releasing it.

With regard to the jqXHR, there's no point in you setting them to null at the end of an AJAX function call. All of the parameters of a AJAX success/error/complete will be released once the function returns by the GC unless jQuery is doing something bizarre like keeping a reference to them.



回答5:

Every variable you can no longer access can be collected by the GC. If you declare a variable inside a function, once the function is quit, the variable can be removed. It is, when the computer runs out of memory, or at any other time.

This becomes more complicated when you execute asynchronous functions like XHR. The done and fail closures can access all variables declared in the outer functions. Thus, as long as done and fail can be executed, all the variables must remain in memory. Once the request is finished, the variables can be released.

Anyway, you should simply make sure that every variable is declared as deep as possible.



回答6:

Is the first example with 'g', g should be set to null. It will retain the pointer to "dd" otherwise.

In the second example, the 'x' in the first case does not need to be set to null, since that variable will "go away" when the surrounding function exits. In the second case, with 'x' outside of the function, 'x' will retain whatever is assigned to it, and that will not be GC'd until 'x' is set to null or something else.