Finding JavaScript memory leaks with Chrome - javascript

I've created a very simple test case that creates a Backbone view, attaches a handler to an event, and instantiates a user-defined class. I believe that by clicking the "Remove" button in this sample, everything will be cleaned up and there should be no memory leaks.
A jsfiddle for the code is here: http://jsfiddle.net/4QhR2/
// scope everything to a function
function main() {
function MyWrapper() {
this.element = null;
}
MyWrapper.prototype.set = function(elem) {
this.element = elem;
}
MyWrapper.prototype.get = function() {
return this.element;
}
var MyView = Backbone.View.extend({
tagName : "div",
id : "view",
events : {
"click #button" : "onButton",
},
initialize : function(options) {
// done for demo purposes only, should be using templates
this.html_text = "<input type='text' id='textbox' /><button id='button'>Remove</button>";
this.listenTo(this,"all",function(){console.log("Event: "+arguments[0]);});
},
render : function() {
this.$el.html(this.html_text);
this.wrapper = new MyWrapper();
this.wrapper.set(this.$("#textbox"));
this.wrapper.get().val("placeholder");
return this;
},
onButton : function() {
// assume this gets .remove() called on subviews (if they existed)
this.trigger("cleanup");
this.remove();
}
});
var view = new MyView();
$("#content").append(view.render().el);
}
main();
However, I am unclear how to use Google Chrome's profiler to verify that this is, in fact, the case. There are a gazillion things that show up on the heap profiler snapshot, and I have no idea how to decode what's good/bad. The tutorials I've seen on it so far either just tell me to "use the snapshot profiler" or give me a hugely detailed manifesto on how the entire profiler works. Is it possible to just use the profiler as a tool, or do I really have to understand how the whole thing was engineered?
EDIT: Tutorials like these:
Gmail memory leak fixing
Using DevTools
Are representative of some of the stronger material out there, from what I've seen. However, beyond introducing the concept of the 3 Snapshot Technique, I find they offer very little in terms of practical knowledge (for a beginner like me). The 'Using DevTools' tutorial doesn't work through a real example, so its vague and general conceptual description of things aren't overly helpful. As for the 'Gmail' example:
So you found a leak. Now what?
Examine the retaining path of leaked objects in the lower half of the Profiles panel
If the allocation site cannot be easily inferred (i.e. event listeners):
Instrument the constructor of the retaining object via the JS console to save the stack trace for allocations
Using Closure? Enable the appropriate existing flag (i.e. goog.events.Listener.ENABLE_MONITORING) to set the creationStack property during construction
I find myself more confused after reading that, not less. And, again, it's just telling me to do things, not how to do them. From my perspective, all of the information out there is either too vague or would only make sense to someone who already understood the process.
Some of these more specific issues have been raised in #Jonathan Naguin's answer below.

A good workflow to find memory leaks is the three snapshot technique, first used by Loreena Lee and the Gmail team to solve some of their memory problems. The steps are, in general:
Take a heap snapshot.
Do stuff.
Take another heap snapshot.
Repeat the same stuff.
Take another heap snapshot.
Filter objects allocated between Snapshots 1 and 2 in Snapshot 3's "Summary" view.
For your example, I have adapted the code to show this process (you can find it here) delaying the creation of the Backbone View until the click event of the Start button. Now:
Run the HTML (saved locally of using this address) and take a snapshot.
Click Start to create the view.
Take another snapshot.
Click remove.
Take another snapshot.
Filter objects allocated between Snapshots 1 and 2 in Snapshot 3's "Summary" view.
Now you are ready to find memory leaks!
You will notice nodes of a few different colors. Red nodes do not have direct references from Javascript to them, but are alive because they are part of a detached DOM tree. There may be a node in the tree referenced from Javascript (maybe as a closure or variable) but is coincidentally preventing the entire DOM tree from being garbage collected.
Yellow nodes however do have direct references from Javascript. Look for yellow nodes in the same detached DOM tree to locate references from your Javascript. There should be a chain of properties leading from the DOM window to the element.
In your particular you can see a HTML Div element marked as red. If you expand the element you will see that is referenced by a "cache" function.
Select the row and in your console type $0, you will see the actual function and location:
>$0
function cache( key, value ) {
// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
if ( keys.push( key += " " ) > Expr.cacheLength ) {
// Only keep the most recent entries
delete cache[ keys.shift() ];
}
return (cache[ key ] = value);
} jquery-2.0.2.js:1166
This is where your element is being referenced. Unfortunally there is not much you can do, it is a internal mechanism from jQuery. But, just for testing purpose, go the function and change the method to:
function cache( key, value ) {
return value;
}
Now if you repeat the process you will not see any red node :)
Documentation:
Eliminating memory leaks in Gmail.
Easing JavaScript Memory Profiling In Chrome DevTools.

Here's a tip on memory profiling of a jsfiddle: Use the following URL to isolate your jsfiddle result, it removes all of the jsfiddle framework and loads only your result.
http://jsfiddle.net/4QhR2/show/
I was never able to figure out how to use the Timeline and Profiler to track down memory leaks, until I read the following documentation. After reading the section entitled 'Object allocation tracker' I was able to use the 'Record Heap Allocations' tool, and track some some Detached DOM nodes.
I fixed the problem by switching from jQuery event binding, to using Backbone event delegation. It's my understanding that newer versions of Backbone will automatically unbind the events for you if you call View.remove(). Execute some of the demos yourself, they are set up with memory leaks for you to identify. Feel free to ask questions here if you still don't get it after studying this documentation.
https://developers.google.com/chrome-developer-tools/docs/javascript-memory-profiling

Basically you need to look at the number of objects inside your heap snapshot. If the number of objects increases between two snapshots and you've disposed of objects then you have a memory leak. My advice is to look for event handlers in your code which do not get detached.

There is an introduction video from Google, which will be very helpful to find JavaScript memory leaks.
https://www.youtube.com/watch?v=L3ugr9BJqIs

You also might want to read :
http://addyosmani.com/blog/taming-the-unicorn-easing-javascript-memory-profiling-in-devtools/
It explains the use of the chrome developer tools and gives some step-by-step advices on how to confirm and locate a memory leak using heap snapshot comparison and the different hep snapshot views available.

You could also look at the Timeline tab in developer tools. Record the usage of your app and keep an eye on the DOM Node and Event listener count.
If the memory graph would indeed indicate a memory leak, then you can use the profiler to figure out what is leaking.

A couple of important notes in regards to identifying memory leaks using Chrome Developer tools:
1) Chrome itself has memory leaks for certain elements such as password and number fields. https://bugs.chromium.org/p/chromium/issues/detail?id=967438. Avoid using those while debugging as they polute your heap snapshot when searching for detached elements.
2) Avoid logging anything to the browser console. Chrome will not garbage collect objects written to the console, hence affecting your result. You can suppress output by placing the following code in the beginning of you script/page:
console.log = function() {};
console.warn = console.log;
console.error = console.log;
3) Use heap snapshots and search for "detach" to identify detached DOM elements. By hovering objects, you get access to all the properties including id and outerHTML which may help identify each element.
If the detached elements are still too generic to recognize, assign them unique IDs using the browser console prior to running your test, e.g.:
var divs = document.querySelectorAll("div");
for (var i = 0 ; i < divs.length ; i++)
{
divs[i].id = divs[i].id || "AutoId_" + i;
}
divs = null; // Free memory
Now, when you identify a detached element with, lets say id="AutoId_49", reload your page, execute the snippet above again, and find the element with id="AutoId_49" using the DOM inspector or document.querySelector(..). Naturally this only works if your page content is predictable.
How I run my tests to identify memory leaks
1) Load page (with console output suppressed!)
2) Do stuff on page that could result in memory leaks
3) Use Developer Tools to take a heap snapshot and search for "detach"
4) Hover elements to identify them from their id or outerHTML properties

I second the advice to take a heap snapshot, they're excellent for detecting memory leaks, chrome does an excellent job of snapshotting.
In my research project for my degree I was building an interactive web application that had to generate a lot of data built up in 'layers', many of these layers would be 'deleted' in the UI but for some reason the memory wasn't being deallocated, using the snapshot tool I was able to determine that JQuery had been keeping a reference on the object (the source was when I was trying to trigger a .load() event which kept the reference despite going out of scope). Having this information at hand single-handedly saved my project, it's a highly useful tool when you're using other people's libraries and you have this issue of lingering references stopping the GC from doing its job.
EDIT:
It's also useful to plan ahead what actions you're going to perform to minimize time spent snapshotting, hypothesize what could be causing the problem and test each scenario out, making snapshots before and after.

Adding my 2 cents here with the tools available in 2021: https://yonatankra.com/how-to-profile-javascript-performance-in-the-browser/
There's a short video version here: https://yonatankra.com/detect-memory-leak-with-chrome-dev-tools

Related

GXT HandlerManager Bus Memory Leak

I'm search for a memory leak in a GWT application. I started with Chrome to create heap dumps and to compare them after certain actions.
In the summary of each heap dump I can see that after big amounts of arrays, Maps, HashMaps one of the biggest groups of classes is the HandlerManager.Bus class. Each HandlerManager.Bus item contains some Maps. Every click they accumulate in the heap and they don't get removed by the GC.
Starting GC manually also does not help.
Further reading shows that HandlerManager has got to do with ClickEvents and MouseEvents. I'm now looking for those events and source code where these are connected to fields of views and grids and other gwt/gxt code.
Please give a some hints what typically can go wrong with these classes.
I assume that there is some static classes and never lose the reference to this events and so the GC cannot remove them.
It is too bad that I cannot follow the memory objects in the heap as easy like I can do in MAT.
Thanks for your suggestions.
I've definitely seen that problem before. You'll notice that all of the calls that add handlers return a HandlerRegistration. Save these in a list. When you are done with a class that contains a list of handlers, go through the list and remove each handler. It's been a while since I've seen any GWT/GXT but I think it goes something like this.
List<HandlerRegistration> handlerList = new LinkedList<>();
public void addListeners() {
handlerList.add(myComboBox.addListener(mySelectionHandler));
}
public void onClose() {
handlerList.forEach(handler -> handler.removeHandler());
}

JavaScript clean memory

I have a problem about memory management.
My simpliest code is here:
var url="/abrapo.php";
var ob={start_:100, token:null}
function post_token(){
$.post(url,{access:ob.token})
.done(function(data){
console.log(data);
ob=data;
});
}
I call function post_token every seconds. So after 2000 call user has problem of memory, ram goes up to 2GB. I don't need to save anything just want to log data after post and clear memory. I've already googled and find delete ob. But it does not clean the memory. What increase memory and how can I clean it without reloading the page
Use your browser's profiling tools to determine where you're accumulating memory. In Chrome these profiling tools are located under the Performance tab in the Chrome Developer Tools F12.
Click the Memory checkbox near the top to enable memory profiling
Click Start Profiling and Reload Page (Ctrl+Shift+E)
Let the profiling run for a while, most pages load in 2-3 seconds but it sounds like your page needs to run longer than that
Click the Stop button to halt the profiling
Among all the other performance graphs, you should see one for memory usage that looks something like this.
You can use this to see if, and when, the browser is performing garbage collections on the Javascript heap. You may need to manually clear certain objects by setting them to null. Also, try to avoid cyclical references and other complex referencing patterns that could cause the javascript engine to hold on to objects longer than it has to.
Click here, for more about the memory management and garbage collection in the browser.
I had similar problems while writing UI for a data acquisition device and managed to make it fly by setting every variable containing large data arrays to null whenever not used.
Your use case isn't easy to replicate with just your code, but I suggest you try setting
data = null;
and
ob = null;
whenever not in use.
You might have to tweak suggestion a bit, say by assigning only token:
ob.token = data.token;
in such case only token would have to be set to null
ob.token = null;
What this achieves essentially is that it gives garbage collector a chance to clear unused objects since variables using those were clearly set to null. Oh, yes, and memory profiling is your friend to find out what exactly should you null
According to your code example, the problem is with console.log(data)
it make your object accessible from Console. The Garbage collection will free memory only when the object is no more accessible.

How to know if element created with document.createElement still exists

I'm creating an anchor element using document.createElement in my PhantomJS tests like this:
var mockLink = null;
beforeEach(function() {
mockLink = document.createElement('a');
});
it('should foo', function() {
// Use mockLink in some way
});
I want to clean up the element in the afterEach code so that each test will have a newly-created instance of it. Unfortunately, element.remove() isn't supported in PhantomJS. Source
afterEach(function() {
mockLink.remove(); // Error!
});
And I want to do this without adding the webpage plugin.
removeChild is supported by PhantomJS, but my newly-created element doesn't have a parent:
mockLink.parentElement // undefined
mockLink.parentElement.removeChild(mockLink); // doesn't work
mockLink.parentNode // also undefined
My newly-created link also doesn't seem to be on the document at all.
mockLink.href = 'www.google.com';
document.getElementsByTagName('a'); // doesn't contain google.com
So I can't do
document.removeChild(mockLink);
mockLink.ownerDocument.contains(mockLink) also returns false
It seems like there are no references to mockLink stored anywhere besides my mockLink variable, meaning that I can just set mockLink equal to null if I want to let that memory be freed up by garbage collection. But how do I verify this works? If I set any other variable to mockLink and then console.log it, it will still come out as defined since it will be a new reference to the same space in memory. How do I verify that mockLink is really being deleted when I set the variable to null?
You've found yourself a conundrum.
If you kill all references to the element by setting mockLink = null and making sure there are no other references to the element, then the garbage collector should be able to free that object.
But, you cannot verify that because in order to verify that the element is no longer available, you'd have to keep a reference to it, but that would prevent it from getting garbage collected in the first place. Thus, the conundrum.
This is an issue that can't really be measured directly from Javascript itself.
If you want to design a one-time test to make sure the memory is being reclaimed, then you can take a memory snapshot, create a several hundred thousand objects stored in an array, clear the array, wait a short time for GC to run, take another memory snapshot, repeat the same process several more times and verify that memory usage of the process is not steadily increasing or study the memory snapshot to make sure none of those objects appear in the memory footprint.
If your javascript environment supports weak references then you can create a weak reference to the node. Depending on the implementation notification mechanisms such as callbacks or reference queues that tell you when it has been collected may also be available.
Unprivileged javascript contexts in web browsers currently don't offer such an API. Node.js, Nashorn and browser addon environments do.

Identify javascript closures with developer tools

I am currently developing a website that is pure javascript and relies heavily on the jQuery & jQuery UI libraries (this site is not intended for use by a general public, hence progressive enhancement is not a strict requirement for this project). I am encountering a significant memory leak on executing the following code:
oDialogBox = $("<div>...</div>");
/* Add useful things to the dialog box here */
oDialogBox.appendTo("body");
oDialogBox.dialog({
/* Other dialog box settings here */
close: function(event, ui) {
oDialogBox.dialog("destroy");
oDialogBox.remove();
oDialogBox = null;
}
});
At any given time in this dialog box, I am creating, removing and modifying a large number of instances of jQuery UI buttons, multiselects (per the Multiselect widget created by Eric Hynds) and on click event handlers. According to jQuery UI documentation, calling .remove() on oDialogBox should result in all child widgets being unbound and deleted. Yet my detached DOM tree shows a significant number of garbage elements that the GC isn't collecting.
It is highly likely I have missed a large set of closures that need to be finished off safely. How do I do the following:
1) How do I identify which closures are keeping a given detached DOM object alive (either in Firefox or Chrome)?
2) Assuming the complete set of closures is identified, does anything beyond nulling the variable need to be done to assure marking the DOM element for garbage collection?
3) I have also noticed my list of arrays stored by the page is giant and contains references to DOM elements not being gathered by the GC. Is there a documented best practice for cleaning arrays from javascript and allowing all elements to be marked for deletion? (Note: this is a current prime suspect for the source of the memory leak)
I'm afraid that I don't have a great answer for #1. I haven't found any really good tools for this myself, even given how good the development tools have become over the last few years. The best advice I can give is to always keep things in the smallest scope you possibly can. If things don't escape, it's generally easier to simply figure out where the references must be.
As to #2, there can be further concerns. If the object referenced by variable v1 closes over the free variables of some function, removing v1 will not be enough to make them eligible for garbage collection if another variable v2 closes over v1 in some other function. So I guess if you really mean the "complete set of closures", then you should be all set. But this might get hairy. Again, if most object have references only in narrow scopes, these problems are much less severe.
For #3, what sorts of arrays are you discussing? If it's jQuery collections, then perhaps you simply have too many of them around. The only reason I know for them to stay around for a long time is to bind event handlers to them, and that is almost always better handled by event delegation on parent elements. If it's you're own custom arrays, do you really have a good reason to store references to them in arrays that last for any substantial length of time? I've rarely found one.

How to destroy a JavaScript object?

Recently, I came across one of my application which consumes too much memory and increasing by 10 MB/sec.
So, I like to know how to destroy JavaScript object and variables so memory consumption stays down and my FF can't get destroyed.
I am calling two of my scripts every 8 seconds without reloading the page.
function refresh() {
$('#table_info').remove();
$('#table').hide();
if (refreshTimer) {
clearTimeout(refreshTimer);
refreshTimer = null ;
}
document.getElementById('refresh_topology').disabled=true;
$('<div id="preload_xml"></div>').html('<img src="pic/dataload.gif" alt="loading data" /><h3>Loading Data...</h3>').prependTo($("#td_123"));
$("#topo").hide();
$('#root').remove();
show_topology();
}
How can I see which variable cause Memory overhead, what's the method to stop the execution of that process?
You could put all of your code under one namespace like this:
var namespace = {};
namespace.someClassObj = {};
delete namespace.someClassObj;
Using the delete keyword will delete the reference to the property, but on the low level the JavaScript garbage collector (GC) will get more information about which objects to be reclaimed.
You could also use Chrome Developer Tools to get a memory profile of your app, and which objects in your app are needing to be scaled down.
You can't delete objects, they are removed when there are no more references to them. You can delete references with delete.
However, if you have created circular references in your objects you may have to de-couple some things.
While the existing answers have given solutions to solve the issue and the second half of the question, they do not provide an answer to the self discovery aspect of the first half of the question that is in bold:
"How can I see which variable causes memory overhead...?"
It may not have been as robust 3 years ago, but the Chrome Developer Tools "Profiles" section is now quite powerful and feature rich. The Chrome team has an insightful article on using it and thus also how garbage collection (GC) works in javascript, which is at the core of this question.
Since delete is basically the root of the currently accepted answer by Yochai Akoka, it's important to remember what delete does. It's irrelevant if not combined with the concepts of how GC works in the next two answers: if there's an existing reference to an object it's not cleaned up. The answers are more correct, but probably not as appreciated because they require more thought than just writing 'delete'. Yes, one possible solution may be to use delete, but it won't matter if there's another reference to the memory leak.
Another answer appropriately mentions circular references and the Chrome team documentation can provide much more clarity as well as the tools to verify the cause.
Since delete was mentioned here, it also may be useful to provide the resource Understanding Delete. Although it does not get into any of the actual solution which is really related to javascript's garbage collector.
Structure your code so that all your temporary objects are located inside closures instead of global namespace / global object properties and go out of scope when you've done with them. GC will take care of the rest.
I was facing a problem like this, and had the idea of simply changing the innerHTML of the problematic object's children.
adiv.innerHTML = "<div...> the original html that js uses </div>";
Seems dirty, but it saved my life, as it works!

Categories

Resources