Finding and debugging memory leaks in JavaScript with Chrome DevTools

Preview:

DESCRIPTION

JavaScript applications are increasingly larger and they also tend to execute during longer periods. In these applications, the memory is a scarce and valuable resource that must be taken care of. With the support of the Chrome Dev Tools, we will learn how to analyze memory consumption and how to find and fix memory leaks, making our applications more stable and robust in the way.

Citation preview

Findinganddebugging

memoryleaksinJavaScriptwith

ChromeDevTools

GonzaloRuizdeVilla@gruizdevilla

Ico-founded&work@adesis

Thispresentationwasmakeformyworkshopat#spainjs2013

$whoami

$aboutthis

Whatisamemoryleak?

Graduallossofavailablecomputermemory

whenaprogramrepeatedly

failstoreturnmemorythatithasobtainedfortemporaryuse.

Myusershavelaptopswith16GBofRAM.

So,whyshouldIcare?

Commonbelief

Morememory===Betterperformance

Reality

Memoryfootprint

isstronglycorrelatedwith

increasedlatenciesandvariance

Nothingisfree:

(cheaporexpensive)

youwillalwayspayapricefortheresourcesyouuse

So,let'stalkaboutmemory

Thinkofmemoryasagraph

Threeprimitivetypes:

Numbers(e.g,3.14159...)Booleans(trueorfalse)

Strings(e.g,"WernerHeisenberg")

Theycannotreferenceothervalues.Theyarealwaysleafsorterminatingnodes.

Everythingelseisan"Object"

Objectsareassociativearrays(mapsordictionaries)

So,theobjectiscomposedofacollectionof(key,value)pairs

AndwhataboutArrays?

AnArrayisanObjectwithnumerickeys.

Thememorygraphstartswitharoot

Itmaybethe objectofthebrowser,orthe objectofaNode.jsmodule.

windowGlobalYoudon'tcontrolhowthisrootobjectisGC

WhatdoesgetGC?Whateverisnotreachablefromtheroot.

RetainingpathWecallaretainingpathanypathfromGCrootstoaparticularobject

Dominators

Node1dominatesnode2Node2dominatesnodes3,4and6Node3dominatesnode5Node5dominatesnode8Node6dominatesnode7

SomefactsabouttheV8GarbageCollector

GenerationalCollector

Ageofavalue

YoungGeneration

OldGeneration

Theageofavalue:numberofbytesallocatedsinceitwasallocated.

Splitedintwospaces:named"to"and"from""tospace":veryfastallocationfillingthe"tospace"triggersacollection:

"to"and"from"swapmaybepromotiontooldgeneration~10ms(remember60fps->~16ms)

Oldgenerationcollectionisslow.

"To"and"From"spaces

Remember:triggeringa

collectionpausesyourapplication.

Somede-referencecommonerrors

Becarefulwitthedeletekeyword.

"o"becomesanSLOWobject.

varo={x:"y"};deleteo.x;o.x;//undefined

varo={x:"y"};o=null;o.x;//TypeError

Itisbettertoset"null".

Onlywhenthelastreferencetoanobjectisremoved,isthatobjecteligibleforcollection.

Awordon"slow"objectsV8optimizingcompilermakesassumptionsonyourcodetomakeoptimizations.

Ittransparentlycreateshiddenclassesthatrepresentyourobjects.

Usingthishiddenclasses,V8worksmuchfaster.Ifyou"delete"properties,theseassumptionsarenolongervalid,andthecodeisde-optimized,slowingyourcode.

FastObject SlowObject

"slow"shouldbeusingasmallermemoryfootprintthan"fast"(1lessproperty),shouldn'tit?

functionSlowPurchase(units,price){this.units=units;this.price=price;this.total=0;this.x=1;}varslow=newSlowPurchase(3,25);//xpropertyisuseless//soIdeleteitdeleteslow.x;

"fast"objectsarefaster

functionFastPurchase(units,price){this.units=units;this.price=price;this.total=0;this.x=1;}varfast=newFastPurchase(3,25);

REALITY:"SLOW"isusing15timesmorememory

TimersTimersareaverycommonsource

ofmemoryleaks.Lookatthefollowingcode:

Ifwerun:Withthiswehaveamemoryleak:

varbuggyObject={callAgain:function(){varref=this;varval=setTimeout(function(){console.log('Calledagain:'+newDate().toTimeString());ref.callAgain();},1000);}};

buggyObject.callAgain();buggyObject=null;

ClosuresClosurescanbeanothersourceofmemoryleaks.Understandwhat

referencesareretainedintheclosure.

Andremember:evalisevil

vara=function(){varlargeStr=newArray(1000000).join('x');returnfunction(){returnlargeStr;};}();

vara=function(){varsmallStr='x',largeStr=newArray(1000000).join('x');returnfunction(n){returnsmallStr;};}();

vara=function(){varsmallStr='x',largeStr=newArray(1000000).join('x');returnfunction(n){eval('');//maintainsreferencetolargeStrreturnsmallStr;};}();

DOMleaksarebiggerthanyouthinkWhenisthe#treeGC?

#leafmaintainsareferencetoit'sparent(parentNode),andrecursivelyupto#tree,soonlywhenleafRefisnullifiedistheWHOLEtreeunder#treecandidatetobeGC

varselect=document.querySelector;vartreeRef=select("#tree");varleafRef=select("#leaf");varbody=select("body");body.removeChild(treeRef);//#treecan'tbeGCyetduetotreeReftreeRef=null;//#treecan'tbeGCyet,dueto//indirectreferencefromleafRefleafRef=null;//NOWcanbe#treeGC

E

Rulesofthumb

Useappropiatescope

Unbindeventlisteners

Managelocalcache

Betterthande-referencing,uselocalscopes.

Unbindeventsthatarenolongerneeded,speciallyiftherelatedDOMobjectsaregoingtoberemoved.

Becarefulwithstoringlargechunksofdatathatyouarenotgoingtouse.

ObjectPoolsYounggenerationGCtakesabout10ms.

Maybeitistoomuchtimeforyou:

Insteadofallocatinganddeallocatingobjects,reusethemwithobjectpools.

Note:objectpoolshavetheirowndrawbacks(forexample,cleaningusedobjects)

Threekeyquestions1. Areyouusingtoomuchmemory?

2. Doyouhavememoryleaks?

3. IsyourappGCingtoooften?

Knowingyourarsenal

BrowserInfoYoucanmeasurehowyourusersareusing

memory.

Youcanmonitortheiractivitytodetect

unexpecteduseofmemory

(onlyinChrome)

>performance.memoryMemoryInfo{jsHeapSizeLimit:793000000,usedJSHeapSize:27600000,totalJSHeapSize:42100000}

jsHeapSizeLimit

usedJSHeapSize

totalJSHeapSize

theamountofmemorythatJavaScriptheapislimitedto

theamountofmemorythatJavaScripthasallocated(includingfreespace)

theamountofmemorycurrentlybeingused

IfusedJSHeapSizegrowsclosetojsHeapSizeLimitthereisariskof:

Imean...

ChromeDevToolsCtrl+Shift+I

⌥⌘Ihttps://developers.google.com/chrome-developer-tools/

Memorytimeline

MemoryProfilingTakingsnapshots

ReadingyourresultsSummary

EYE-CATCHINGTHINGSINTHESUMMARY

Distance:distancefromtheGCroot.

Ifalmostalltheobjectsofthesametypeareatthesamedistance,

andafewareatabiggerdistance,that'ssomethingworthinvestigating.

Areyouleakingthelatterones?

MOREEYE-CATCHINGTHINGSINTHESUMMARY

Retainingmemory:thememoryusedbytheobjects

ANDtheobjectstheyarereferencing.Useittoknowwhereareyouusingmostofthememory.

ATIPABOUTCLOSURESIthelpsalottonamethefunctions,soyoueasilydistinguishbetween

closuresinthesnapshot.functioncreateLargeClosure(){varlargeStr=newArray(1000000).join('x');varlC=function(){//thisISNOTanamedfunctionreturnlargeStr;};returnlC;}

functioncreateLargeClosure(){varlargeStr=newArray(1000000).join('x');varlC=functionlC(){//thisISanamedfunctionreturnlargeStr;};returnlC;}

Switchingbetweensnapshotsviews

Summary:groupsbyconstructornameComparison:comparestwosnapshots

Containment:bird'seyeviewoftheobjectstructureDominators:usefultofindaccumulationpoints

Understandingnodecolors

Yellow:objecthasaJavaScriptreferenceonit

Red:detachednode.Referencedfromonewithyellowbackground

YoucanforceGCfromChromeDevTools

WhentakingaHeapSnapshot,itisautomaticallyforced.InTimeline,itcanbeveryconvenienttoforceaGC.

MemoryleakpatternSomenodesarenotbeingcollected:

The3snapshottechnique

Rationale

Yourlongrunningapplicationisinanstationarystate.

Memoryoscillatesaroundaconstantvalue.

(orhasaconstant,controlled,expectedandjustifiedgrowth).

Whatdoweexpect?

Newobjectstobeconstantlyandconsistentlycollected.

Let'ssaywestartfromasteadystate:

Checkpoint#1

WedosomestuffCheckpoint#2

WerepeatthesamestuffCheckpoint#3

Again,whatshouldweexpect?

AllnewmemoryusedbetweenCheckpoint#1andCheckpoint#2

hasbeencollected.

NewmemoryusedbetweenCheckpoint#2andCheckpoint#3maystillbeinuseinCheckpoint

#3

ThestepsOpenDevToolsTakeaheapsnapshot#1PerformsuspiciousactionsTakeaheapsnapshot#2PerformsameactionsagainTakeathirdheapsnapshot#3Selectthissnapshot,andselect"ObjectsallocatedbetweenSnapshots1and2"

The3snapshottechniqueevolved

Simpler&morepowerfulbut...Doyou

haveChromeCanaryinstalled?

Brandnewfeature:

RecordHeapAllocations

Bluebars:memoryallocations.Tallerequalsmorememory.Greybars:deallocated

Let'splay!Youcangetthecodefrom:

https://github.com/gonzaloruizdevilla/debuggingmemory.git

Oryoucanuse:

http://goo.gl/4SK53

Thankyou!gonzalo.ruizdevilla@adesis.com

@gruizdevilla

(btw,wearehiring!)

Recommended