plugging holes — javascript memory leak debugging

Post on 29-Nov-2014

532 Views

Category:

Software

2 Downloads

Preview:

Click to see full reader

DESCRIPTION

 

TRANSCRIPT

PLUGGINGHOLES

JAVASCRIPT MEMORY LEAKDEBUGGING

Christian Speckner, Mayflower GmbH

WHAT IS A MEMORYLEAK?

WHAT DOES WIKIPEDIA SAY?“In computer science, a memory leak

occurs when a computer programincorrectly manages memory

allocations”

MORE CONCISELY:Memory leaks occure if memory is repeatedly allocated

without being freed again by the program.

WHAT IS MEMORY ALLOCATIONIN JAVASCRIPT?

Memory allocation = Object creation:

... more ways to create objects

var foo = [1, 2, 3];

var foo = {bar: "baz"};

var foo = new Thing();

var foo = function() {};

var foo = document.createElement("div");

HOW IS MEMORY FREED INJAVASCRIPT?

GARBAGE COLLECTION

HOW DOES GARBAGECOLLECTION WORK?

Objects are eligible for Garbage Collection once theycease to be "alive"

Each object referenced by a "living" object is alivecounted as aliveThe garbage collector roots are alive per definition

→ A living object is is part of a retaining tree which startsat a garbage collection root

OBJECT REFERENCESExamples of Object References:

Scope variablesObject propertiesFunction closures

GARBAGE COLLECTOR ROOTSWhat are the garbage collector roots?

Internals of the VM implementationFor our purposes: window can be treated like a root("dominator")

MEMORY LEAKS INJAVASCRIPT

Memory leaks occur if objects are repeatedly created andcannot be reclaimed by the garbage collector due to

remaining references.

WHY SHOULD I CARE?

THAT DEPENDS—

HOW DOES YOURAPPLICATION LOOK

LIKE?

1. LINKED WEBPAGES, A LITTLE JSON EACH PAGE

Navigating between pages resets the VMScript does not execute over extended periods of timeLeaks have no time to cause trouble

→ NOTHING TO WORRY ABOUT

2. SINGLE PAGE JS APPLICATION

All content generated by JS programProgram runs as long as the page is openSmall leaks accumulateBrowser 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 SCOPEVARIABLES

function foo() { var garbage = {dead: "meat"}; return "Hello world!";}

foo();

1. Object stays referenced through garbage while fooexecutes

2. garbage goes out of scope once foo terminates3. No more references to object → can be garbage

collected

No big deal as long as we avoid global variables

REFERENCES THROUGH OBJECTPROPERTIES

var vault = {};function foo() { var garbage = {dead: "meat"}; vault.entry = garbage; return "Hello world!";}

foo();

Object remains referenced via vault.entry even afterfoo terminatesNeed either to reset vault.entry or remove allreferences to vault

Take care that no other objects retain references tounused objects — maps / registries, I'm looking at you :)

REFERENCES THROUGHFUNCTION CLOSURES

function foo() { var garbage = {dead: "meat"}; return function() { return garbage.dead; };}

var bar = foo();

Object is referenced by the returned function closureeven after foo terminates!The object is only cleaned up after the functionreferenced by bar is garbage collected

With great power comes great responsibility: be carefulwhen passing around closures!

DANGEROUS SPECIAL CASE:EVENT HANDLERS

function Counter(elt) { var me = this; me.count = 0;

elt.addEventListener("mousedown", function() { me.count++; }}var counter = new Counter(document);

The event handler keeps a reference to the counterinstanceWe have no direct reference to the handlerGarbage collection will never happen!

Always leave a way to clean out event handlers (and doit!), don't throw away those references!

DANGEROUS SPECIAL CASE:DETACHED DOM TREES

var div = 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 globalregistry...... but remove it from the DOM when clearing its parentNode remains referenced → detached DOM tree!

Take care to hold no references into DOM trees after theybecome detached!

HOW TO FIND MEMORYLEAKS?

GOOGLE CHROMETOOLS!

MEMORY CONSUMPTIONTIMELINE

For a healthy application, memory consumption shouldRise over time as objects are allocatedGo down when garbage is collected → sawtooth patternBaseline after garbage collection should not change

Growing consumption after GC indicates a leak

HEAP SNAPSHOTSCapture a snapshot of the heap contestsCan be filtered by constructorLiving objects can be inspectedAllocated memory sizeDifferences between snapshots: what leaks?Retaining trees: why does it leak?

Retaining tree allows to track down the references thathave to be cleaned out!

SOME HINTS FOR READING THERETAINING TREE

The shortest path is usually most interestingInspect what you can — know your enemyFollow the black objects; gray ones are internals'context' indicates a function scope'native' on a DOM node indicates an event handlerTake your time!

HEAP ALLOCATION TIMELINEShows allocation eventsAllocations which have been freed turn light blueHeap can be inspected filtered based on the time ofallocation

Allows to correlate heap state with events → identify thecode paths that leak

SOME TRICKS

TRACKING INDIVUAL OBJECTSfunction Marker(id) { this.id = id;}

var trackedInstance = new Thing();trackedInstance.__marker = new Marker('someId');

Named constructors can be easily found in heapsnapshotsMarker is not collected as long as trackedInstanceremains aliveAllows to identify individual objects in heap

More generally: naming function makes heap snapshotsmuch easier to read

DEALING WITH LEAKY LIBRARIESCommon situation:

var snark = new library.Snark();snark.on('bark', someObject.handler, someObject);

// ...

snark.destroy();

Assuming that the library is buggy, it could retain areference to someObject, leading to a leak.

Improvement:var snark = new library.Snark(), scope = {instance: someObject};

snark.on('bark', function() {this.instance.handler()}, scope);

// ...

snark.destroy();scope.instance = null;}

Reference is now indirect via scopeResetting scope.instance breaks referenceThe library can't prevent someObject from beinggarbage collectedLeak is reduced to scope object!

GENERAL ADVICEProfile memory usage early and oftenBe extra careful with closuresUnbind event listenersBe careful with registriesConsider object pooling

THAT'S ALL —

THANK YOU FOR YOURATTENTION!

top related