KEMBAR78
Plugging holes — javascript memory leak debugging | PDF
PLUGGING
HOLES
JAVASCRIPT MEMORY LEAK
DEBUGGING
Christian Speckner, Mayflower GmbH
WHAT IS A MEMORY
LEAK?
WHAT DOES WIKIPEDIA SAY?
“In computer science, a memory leak
occurs when a computer program
incorrectly manages memory
allocations”
MORE CONCISELY:
Memory leaks occure if memory is repeatedly allocated
without being freed again by the program.
WHAT IS MEMORY ALLOCATION
IN JAVASCRIPT?
Memory allocation = Object creation:
... more ways to create objects
varfoo=[1,2,3];
varfoo={bar:"baz"};
varfoo=newThing();
varfoo=function(){};
varfoo=document.createElement("div");
HOW IS MEMORY FREED IN
JAVASCRIPT?
GARBAGE COLLECTION
HOW DOES GARBAGE
COLLECTION WORK?
Objects are eligible for Garbage Collection once they
cease to be "alive"
Each object referenced by a "living" object is alive
counted as alive
The garbage collector roots are alive per definition
→ A living object is is part of a retaining tree which starts
at a garbage collection root
OBJECT REFERENCES
Examples of Object References:
Scope variables
Object properties
Function closures
GARBAGE COLLECTOR ROOTS
What are the garbage collector roots?
Internals of the VM implementation
For our purposes: windowcan be treated like a root
("dominator")
MEMORY LEAKS IN
JAVASCRIPT
Memory leaks occur if objects are repeatedly created and
cannot be reclaimed by the garbage collector due to
remaining references.
WHY SHOULD I CARE?
THAT DEPENDS
—
HOW DOES YOUR
APPLICATION LOOK
LIKE?
1. LINKED WEBPAGES, A LITTLE JS
ON EACH PAGE
Navigating between pages resets the VM
Script does not execute over extended periods of time
Leaks have no time to cause trouble
→ NOTHING TO WORRY ABOUT
2. SINGLE PAGE JS APPLICATION
All content generated by JS program
Program runs as long as the page is open
Small leaks accumulate
Browser swallows 100s MB of RAM before crashing!
→ POTENTIAL SHOWSTOPPER!
HOW TO AVOID LEAKS?
Simple answer: Always be sure to delete all references to
unused objects.
REFERENCES VIA SCOPE
VARIABLES
functionfoo(){
vargarbage={dead:"meat"};
return"Helloworld!";
}
foo();
1. Object stays referenced through garbagewhile foo
executes
2. garbagegoes out of scope once footerminates
3. No more references to object → can be garbage
collected
No big deal as long as we avoid global variables
REFERENCES THROUGH OBJECT
PROPERTIES
varvault={};
functionfoo(){
vargarbage={dead:"meat"};
vault.entry=garbage;
return"Helloworld!";
}
foo();
Object remains referenced via vault.entryeven after
footerminates
Need either to reset vault.entryor remove all
references to vault
Take care that no other objects retain references to
unused objects — maps / registries, I'm looking at you :)
REFERENCES THROUGH
FUNCTION CLOSURES
functionfoo(){
vargarbage={dead:"meat"};
returnfunction(){
returngarbage.dead;
};
}
varbar=foo();
Object is referenced by the returned function closure
even after footerminates!
The object is only cleaned up after the function
referenced by baris garbage collected
With great power comes great responsibility: be careful
when passing around closures!
DANGEROUS SPECIAL CASE:
EVENT HANDLERS
functionCounter(elt){
varme=this;
me.count=0;
elt.addEventListener("mousedown",function(){
me.count++;
}
}
varcounter=newCounter(document);
The event handler keeps a reference to the counter
instance
We have no direct reference to the handler
Garbage collection will never happen!
Always leave a way to clean out event handlers (and do
it!), don't throw away those references!
DANGEROUS SPECIAL CASE:
DETACHED DOM TREES
vardiv=document.createElement('div');
myFancyElementRegistry.register(div,'somediv');
document.querySelector('.container').appendChild(div);
//...
document.querySelector('.container').innerHTML='';
We keep a reference to the newly created div in a global
registry...
... but remove it from the DOM when clearing its parent
Node remains referenced → detached DOM tree!
Take care to hold no references into DOM trees after they
become detached!
HOW TO FIND MEMORY
LEAKS?
GOOGLE CHROME
TOOLS!
MEMORY CONSUMPTION
TIMELINE
For a healthy application, memory consumption should
Rise over time as objects are allocated
Go down when garbage is collected → sawtooth pattern
Baseline after garbage collection should not change
Growing consumption after GC indicates a leak
HEAP SNAPSHOTS
Capture a snapshot of the heap contests
Can be filtered by constructor
Living objects can be inspected
Allocated memory size
Differences between snapshots: what leaks?
Retaining trees: why does it leak?
Retaining tree allows to track down the references that
have to be cleaned out!
SOME HINTS FOR READING THE
RETAINING TREE
The shortest path is usually most interesting
Inspect what you can — know your enemy
Follow the black objects; gray ones are internals
'context' indicates a function scope
'native' on a DOM node indicates an event handler
Take your time!
HEAP ALLOCATION TIMELINE
Shows allocation events
Allocations which have been freed turn light blue
Heap can be inspected filtered based on the time of
allocation
Allows to correlate heap state with events → identify the
code paths that leak
SOME TRICKS
TRACKING INDIVUAL OBJECTS
functionMarker(id){
this.id=id;
}
vartrackedInstance=newThing();
trackedInstance.__marker=newMarker('someId');
Named constructors can be easily found in heap
snapshots
Marker is not collected as long as trackedInstance
remains alive
Allows to identify individual objects in heap
More generally: naming function makes heap snapshots
much easier to read
DEALING WITH LEAKY LIBRARIES
Common situation:
varsnark=newlibrary.Snark();
snark.on('bark',someObject.handler,someObject);
//...
snark.destroy();
Assuming that the library is buggy, it could retain a
reference to someObject, leading to a leak.
Improvement:
varsnark=newlibrary.Snark(),
scope={instance:someObject};
snark.on('bark',function(){this.instance.handler()},scope);
//...
snark.destroy();
scope.instance=null;
}
Reference is now indirect via scope
Resetting scope.instancebreaks reference
The library can't prevent someObjectfrom being
garbage collected
Leak is reduced to scope object!
GENERAL ADVICE
Profile memory usage early and often
Be extra careful with closures
Unbind event listeners
Be careful with registries
Consider object pooling
THAT'S ALL
—
THANK YOU FOR YOUR
ATTENTION!

Plugging holes — javascript memory leak debugging