Post on 19-Jan-2015
description
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!)