How Do You Know if You Have Memory Leaks

In this article we will explore common types of retention leaks in client-side JavaScript code. Nosotros will also larn how to use the Chrome Development Tools to find them. Read on!


Introduction

Memory leaks are a trouble every developer has to confront eventually. Even when working with retention-managed languages there are cases where retention tin exist leaked. Leaks are the cause of whole class of issues: slowdowns, crashes, high latency, and even problems with other applications.

What are memory leaks?

In essence, memory leaks can be defined every bit retentivity that is non required by an application anymore that for some reason is not returned to the operating organization or the pool of gratuitous retentiveness. Programming languages favor different ways of managing memory. These ways may reduce the chance of leaking memory. However, whether a sure piece of retentiveness is unused or non is actually an undecidable problem. In other words, only developers tin can brand information technology articulate whether a piece of retentiveness can be returned to the operating system or non. Certain programming languages provide features that assist developers do this. Others expect developers to be completely explicit nearly when a piece of memory is unused. Wikipedia has good articles on manual and automatic memory management.

Retentivity direction in JavaScript

JavaScript is i of the then called garbage collected languages. Garbage collected languages assistance developers manage memory past periodically checking which previously allocated pieces of retention can nevertheless be "reached" from other parts of the application. In other words, garbage collected languages reduce the problem of managing memory from "what memory is notwithstanding required?" to "what memory can still exist reached from other parts of the application?". The deviation is subtle, but important: while merely the developer knows whether a slice of allocated memory volition be required in the future, unreachable memory can exist algorithmically adamant and marked for return to the Os.

Non-garbage-collected languages commonly employ other techniques to manage retentiveness: explicit management, where the developer explicitly tells the compiler when a piece of retention is not required; and reference counting, in which a use count is associated with every block of retentiveness (when the count reaches goose egg information technology is returned to the OS). These techniques come up with their own trade-offs (and potential causes for leaks).

Leaks in JavaScript

The principal cause for leaks in garbage collected languages are unwanted references. To understand what unwanted references are, start nosotros need to understand how a garbage collector determines whether a piece of memory can be reached or not.

"The main cause for leaks in garbage collected languages are unwanted references."

Tweet

Tweet This

Mark-and-sweep

Most garbage collectors use an algorithm known as mark-and-sweep. The algorithm consists of the following steps:

  1. The garbage collector builds a list of "roots". Roots commonly are global variables to which a reference is kept in code. In JavaScript, the "window" object is an case of a global variable that can act as a root. The window object is always present, so the garbage collector can consider information technology and all of its children to be ever nowadays (i.e. not garbage).
  2. All roots are inspected and marked equally agile (i.e. not garbage). All children are inspected recursively as well. Everything that can be reached from a root is not considered garbage.
  3. All pieces of retentivity not marked as active tin can now be considered garbage. The collector can now free that memory and return it to the OS.

Modernistic garbage collectors better on this algorithm in dissimilar ways, but the essence is the same: reachable pieces of retentivity are marked as such and the rest is considered garbage.

Unwanted references are references to pieces of memory that the developer knows he or she won't be needing anymore but that for some reason are kept within the tree of an agile root. In the context of JavaScript, unwanted references are variables kept somewhere in the code that will not be used anymore and point to a slice of retention that could otherwise be freed. Some would debate these are programmer mistakes.

And then to understand which are the nearly mutual leaks in JavaScript, we need to know in which means references are unremarkably forgotten.

The Three Types of Common JavaScript Leaks

i: Adventitious global variables

One of the objectives behind JavaScript was to develop a language that looked like Java but was permissive enough to be used by beginners. 1 of the ways in which JavaScript is permissive is in the way it handles undeclared variables: a reference to an undeclared variable creates a new variable inside the global object. In the case of browsers, the global object is window. In other words:

                      function            foo            (            arg            )            {            bar            =            "this is a hidden global variable"            ;            }                  

Is in fact:

                      office            foo            (            arg            )            {            window.bar            =            "this is an explicit global variable"            ;            }                  

If bar was supposed to concord a reference to a variable simply inside the scope of the foo function and y'all forget to use var to declare it, an unexpected global variable is created. In this example, leaking a uncomplicated string won't practice much harm, but it could certainly be worse.

Some other mode in which an adventitious global variable can exist created is through this :

                      office            foo            (            )            {            this            .variable            =            "potential adventitious global"            ;            }            // Foo called on its own, this points to the global object (window)            // rather than being undefined.            foo            (            )            ;                  

To prevent these mistakes from happening, add 'use strict' ; at the beginning of your JavaScript files. This enables a stricter mode of parsing JavaScript that prevents adventitious globals.

A annotation on global variables

Fifty-fifty though nosotros talk about unsuspected globals, it is notwithstanding the example that much lawmaking is littered with explicit global variables. These are past definition noncollectable (unless nulled or reassigned). In particular, global variables used to temporarily store and process large amounts of data are of concern. If you must use a global variable to shop lots of data, make sure to null it or reassign it after you lot are done with it. I mutual crusade for increased memory consumption in connexion with globals are caches). Caches store data that is repeatedly used. For this to exist efficient, caches must take an upper spring for its size. Caches that grow unbounded tin result in high memory consumption because their contents cannot be nerveless.

ii: Forgotten timers or callbacks

The use of setInterval is quite common in JavaScript. Other libraries provide observers and other facilities that take callbacks. About of these libraries take intendance of making whatever references to the callback unreachable after their own instances become unreachable besides. In the case of setInterval, however, code like this is quite common:

                      var            someResource            =            getData            (            )            ;            setInterval            (            function            (            )            {            var            node            =            certificate.            getElementById            (            'Node'            )            ;            if            (node)            {            // Practice stuff with node and someResource.            node.innerHTML            =            JSON            .            stringify            (someResource)            )            ;            }            }            ,            m            )            ;                  

This example illustrates what tin can happen with dangling timers: timers that make reference to nodes or data that is no longer required. The object represented past node may be removed in the future, making the whole block inside the interval handler unnecessary. However, the handler, as the interval is still agile, cannot exist collected (the interval needs to be stopped for that to happen). If the interval handler cannot be collected, its dependencies cannot exist nerveless either. That ways that someResource, which presumably stores sizable information, cannot be collected either.

For the case of observers, it is important to brand explicit calls to remove them once they are non needed anymore (or the associated object is virtually to be made unreachable). In the past, this used to be especially important as sure browsers (Net Explorer 6) were not able to manage cyclic references well (meet below for more info on that). Present, almost browsers can and volition collect observer handlers one time the observed object becomes unreachable, even if the listener is not explicitly removed. It remains skilful practice, nevertheless, to explicitly remove these observers before the object is disposed. For instance:

                      var            chemical element            =            certificate.            getElementById            (            'push'            )            ;            function            onClick            (            effect            )            {            element.innerHtml            =            'text'            ;            }            chemical element.            addEventListener            (            'click'            ,            onClick)            ;            // Do stuff            element.            removeEventListener            (            'click'            ,            onClick)            ;            chemical element.parentNode.            removeChild            (element)            ;            // Now when element goes out of scope,            // both element and onClick will be collected fifty-fifty in onetime browsers that don't            // handle cycles well.                  

A note nigh object observers and cyclic references

Observers and cyclic references used to be the bane of JavaScript developers. This was the case due to a bug (or blueprint determination) in Internet Explorer'southward garbage collector. Sometime versions of Cyberspace Explorer could non detect cyclic references between DOM nodes and JavaScript lawmaking. This is typical of an observer, which commonly keeps a reference to the observable (as in the case above). In other words, every time an observer was added to a node in Internet Explorer, it resulted in a leak. This is the reason developers started explicitly removing handlers earlier nodes or nulling references within observers. Nowadays, modernistic browsers (including Internet Explorer and Microsoft Edge) apply modern garbage collection algorithms that tin find these cycles and deal with them correctly. In other words, it is not strictly necessary to call removeEventListener before making a node unreachable.

Frameworks and libraries such as jQuery do remove listeners before disposing of a node (when using their specific APIs for that). This is handled internally past the libraries and makes sure that no leaks are produced, even when run nether problematic browsers such equally the onetime Internet Explorer.

3: Out of DOM references

Sometimes it may be useful to store DOM nodes inside data structures. Suppose you lot desire to rapidly update the contents of several rows in a table. It may make sense to store a reference to each DOM row in a dictionary or array. When this happens, two references to the same DOM element are kept: one in the DOM tree and the other in the dictionary. If at some point in the future y'all decide to remove these rows, you need to make both references unreachable.

                      var            elements            =            {            push:            document.            getElementById            (            'button'            )            ,            image:            document.            getElementById            (            'image'            )            ,            text:            certificate.            getElementById            (            'text'            )            }            ;            role            doStuff            (            )            {            image.src            =            'http://some.url/image'            ;            button.            click            (            )            ;            console.            log            (text.innerHTML)            ;            // Much more logic            }            function            removeButton            (            )            {            // The button is a directly child of body.            certificate.body.            removeChild            (document.            getElementById            (            'button'            )            )            ;            // At this point, we still have a reference to #button in the global            // elements lexicon. In other words, the button chemical element is nonetheless in            // retention and cannot exist collected by the GC.            }                  

An additional consideration for this has to practise with references to inner or leafage nodes inside a DOM tree. Suppose y'all keep a reference to a specific cell of a table (a <td> tag) in your JavaScript code. At some point in the hereafter you decide to remove the tabular array from the DOM merely proceed the reference to that cell. Intuitively i may suppose the GC will collect everything but that cell. In practice this won't happen: the cell is a child node of that table and children keep references to their parents. In other words, the reference to the table jail cell from JavaScript lawmaking causes the whole table to stay in retentivity. Consider this carefully when keeping references to DOM elements.

four: Closures

A key aspect of JavaScript development are closures: anonymous functions that capture variables from parent scopes. Meteor developers institute a particular case in which due to implementation details of the JavaScript runtime, it is possible to leak memory in a subtle fashion:

                      var            theThing            =            nothing            ;            var            replaceThing            =            part            (            )            {            var            originalThing            =            theThing;            var            unused            =            function            (            )            {            if            (originalThing)            console.            log            (            "hello"            )            ;            }            ;            theThing            =            {            longStr:            new            Assortment            (            1000000            )            .            join            (            '*'            )            ,            someMethod            :            function            (            )            {            console.            log            (someMessage)            ;            }            }            ;            }            ;            setInterval            (replaceThing,            1000            )            ;                  

This snippet does one thing: every fourth dimension replaceThing is called, theThing gets a new object which contains a big array and a new closure (someMethod). At the same time, the variable unused holds a closure that has a reference to originalThing (theThing from the previous call to replaceThing). Already somewhat confusing, huh? The of import thing is that one time a scope is created for closures that are in the same parent scope, that scope is shared. In this case, the scope created for the closure someMethod is shared by unused. unused has a reference to originalThing. Even though unused is never used, someMethod tin be used through theThing. And every bit someMethod shares the closure scope with unused, fifty-fifty though unused is never used, its reference to originalThing forces it to stay active (prevents its collection). When this snippet is run repeatedly a steady increase in memory usage can be observed. This does not get smaller when the GC runs. In essence, a linked list of closures is created (with its root in the form of the theThing variable), and each of these closures' scopes carries an indirect reference to the big array, resulting in a sizable leak.

This is an implementation antiquity. A different implementation of closures that tin can handle this matter is conceivable, as explained in the Meteor weblog post.

Unintuitive behavior of Garbage Collectors

Although Garbage Collectors are convenient they come up with their ain set of merchandise-offs. 1 of those merchandise-offs is nondeterminism. In other words, GCs are unpredictable. Information technology is not unremarkably possible to exist certain when a collection will exist performed. This means that in some cases more retentivity than is actually required by the program is existence used. In other cases, short-pauses may be noticeable in particularly sensitive applications. Although nondeterminism means one cannot be sure when a drove will be performed, most GC implementations share the common pattern of doing collection passes during allocation. If no allocations are performed, almost GCs stay at residue. Consider the following scenario:

  1. A sizable set of allocations is performed.
  2. Most of these elements (or all of them) are marked as unreachable (suppose nosotros naught a reference pointing to a enshroud we no longer demand).
  3. No farther allocations are performed.

In this scenario, most GCs volition not run any further collection passes. In other words, even though there are unreachable references available for collection, these are not claimed past the collector. These are not strictly leaks, but still consequence in higher-than-usual memory usage.

Google provides an excellent instance of this behavior in their JavaScript Memory Profiling docs, example #two.

Chrome provides a dainty set of tools to contour memory usage of JavaScript lawmaking. There 2 essential views related to memory: the timeline view and the profiles view.

Timeline view

Google Dev Tools Timeline in Action The timeline view is essential in discovering unusual retention patterns in our code. In instance we are looking for big leaks, periodic jumps that practice not shrink equally much equally they grew subsequently a collection are a red flag. In this screenshot nosotros can see what a steady growth of leaked objects can await like. Even afterwards the big collection at the terminate, the total amount of retentiveness used is higher than at the beginning. Node counts are too higher. These are all signs of leaked DOM nodes somewhere in the code.

Profiles view

Google Dev Tools Profiles in Action This is the view you will spend almost of the time looking at. The profiles view allows you to get a snapshot and compare snapshots of the memory use of your JavaScript lawmaking. Information technology also allows you to record allocations along time. In every result view different types of lists are available, only the most relevant ones for our chore are the summary list and the comparing listing.

The summary view gives united states an overview of the different types of objects allocated and their aggregated size: shallow size (the sum of all objects of a specific type) and retained size (the shallow size plus the size of other objects retained due to this object). It also gives us a notion of how far an object is in relation to its GC root (the distance).

The comparing list gives us the same information but allows usa to compare different snapshots. This is specially useful to find leaks.

Example: Finding Leaks Using Chrome

There are substantially ii types of leaks: leaks that cause periodic increases in memory utilise and leaks that happen once and crusade no further increases in memory. For obvious reasons, it is easier to find leaks when they are periodic. These are also the almost troublesome: if memory increases in time, leaks of this type will eventually crusade the browser to become slow or stop execution of the script. Leaks that are non periodic can hands be found when they are large enough to exist noticeable amongst all other allocations. This is usually not the case, and so they usually remain unnoticed. In a way, minor leaks that are happen once could be considered an optimization issue. However, leaks that are periodic are bugs and must exist stock-still.

For our example we will use i of the examples in Chrome'south docs. The full lawmaking is pasted below:

                      var            x            =            [            ]            ;            function            createSomeNodes            (            )            {            var            div,            i            =            100            ,            frag            =            document.            createDocumentFragment            (            )            ;            for            (            ;i            >            0            ;            i--            )            {            div            =            document.            createElement            (            "div"            )            ;            div.            appendChild            (certificate.            createTextNode            (i            +            " - "            +            new            Date            (            )            .            toTimeString            (            )            )            )            ;            frag.            appendChild            (div)            ;            }            document.            getElementById            (            "nodes"            )            .            appendChild            (frag)            ;            }            function            grow            (            )            {            x.            button            (            new            Array            (            1000000            )            .            join            (            'ten'            )            )            ;            createSomeNodes            (            )            ;            setTimeout            (abound,            1000            )            ;            }                  

When grow is invoked it will outset creating div nodes and appending them to the DOM. Information technology will also allocate a big array and append information technology to an array referenced by a global variable. This will crusade a steady increase in retention that can be constitute using the tools mentioned above.

Garbage nerveless languages usually prove a pattern of oscillating retention use. This is expected if code is running in a loop performing allocations, which is the usual case. We will be looking for periodic increases in memory that do not fall back to previous levels after a collection.

Find out if retentiveness is periodically increasing

The timeline view is great for this. Open the example in Chrome, open the Dev Tools, go to timeline, select retention and click the record button. And then get to the page and click The Button to outset leaking retentiveness. Later on a while stop the recording and take a look at the results:

Memory leaks in the timeline view

This example will keep leaking retentiveness each 2d. After stopping the recording, set a breakpoint in the abound function to cease the script from forcing Chrome to close the page.

There are two big signs in this image that show nosotros are leaking retentivity. The graphs for nodes (green line) and JS heap (blue line). Nodes are steadily increasing and never subtract. This is a big alarm sign.

The JS heap besides shows a steady increase in memory apply. This is harder to see due to the event of the garbage collector. You can see a pattern of initial memory growth, followed by a big subtract, followed by an increase so a spike, continued by another drop in memory. The key in this case lies in the fact that afterwards each drop in memory utilize, the size of the heap remains bigger than in the previous drop. In other words, although the garbage collector is succeeding in collecting a lot of retentiveness, some of it is periodically being leaked.

Nosotros are now certain nosotros accept a leak. Allow's find information technology.

Go two snapshots

To notice a leak we will now get to the profiles section of Chrome's Dev Tools. To keep retentiveness use in a manageable levels, reload the page earlier doing this step. We volition utilise the Take Heap Snapshot function.

Reload the page and take a heap snapshot right afterward it finishes loading. We volition utilise this snapshot every bit our baseline. Later that, hit The Push again, wait a few seconds, and accept a 2nd snapshot. Afterwards the snapshot is taken, it is advisable to prepare a breakpoint in the script to stop the leak from using more memory.

Heap Snapshots

There are 2 ways in which we can take a expect at allocations between the two snapshots. Either select Summary and and so to the right pick Objects allocated between Snapshot 1 and Snapshot 2, or select Comparison rather than Summary. In both cases we will meet a list of objects that were allocated between the 2 snapshots.

In this example information technology is quite like shooting fish in a barrel to find the leaks: they are big. Have a look at the Size Delta of the (string) constructor. 8MBs with 58 new objects. This looks suspicious: new objects are allocated but not freed and 8MBs get consumed.

If nosotros open the list of allocations for the (string) constructor we will observe there are a few large allocations among many small ones. The big ones immediately telephone call our attention. If we select any unmarried one of them nosotros go something interesting in the retainers department below.

Retainers for selected object

Nosotros see our selected allocation is part of an assortment. In turn, the assortment is referenced by variable 10 inside the global window object. This gives u.s.a. a total path from our big object to its noncollectable root (window). We institute our potential leak and where information technology is referenced.

So far so good. Just our case was piece of cake: big allocations such as the one in this example are not the norm. Fortunately our example is likewise leaking DOM nodes, which are smaller. Information technology is easy to find these nodes using the snapshots above, just in bigger sites, things become messier. Contempo versions of Chrome provide an additional tool that is best suited for our job: the Tape Heap Allocations role.

Recording heap allocations to find leaks

Disable the breakpoint you set before, let the script continue running, and go back to the Profiles section of Chrome'southward Dev Tools. At present hit Record Heap Allocations. While the tool is running you will discover blue spikes in the graph at the top. These correspond allocations. Every 2nd a big allotment is performed by our code. Let information technology run for a few seconds and then stop it (don't forget to set the breakpoint once again to forbid Chrome from eating more memory).

Recorded heap allocations

In this image y'all can see the killer feature of this tool: selecting a piece of the timeline to see what allocations where performed during that time span. We set the selection to be as close to one of the big spikes as possible. Merely three constructors are shown in the listing: one of them is the one related to our big leaks ( (string) ), the next one is related to DOM allocations, and the last one is the Text constructor (the constructor for leaf DOM nodes containing text).

Select 1 of the HTMLDivElement constructors from the listing and and so pick Allotment stack.

Selected element in heap allocation results

BAM! We at present know where that element was allocated (abound -> createSomeNodes). If we pay shut attention to each spike in the graph we volition detect that the HTMLDivElement constructor is being called a lot. If we go back to our snapshot comparison view we will notice that this constructor shows many allocations but no deletions. In other words, it is steadily allocating memory without allowing the GC to reclaim some of it. This has all the signs of a leak plus we know exactly where these objects are being allocated (the createSomeNodes office). Now its time to go back to the code, study it, and fix the leaks.

Another useful feature

In the heap allocations result view nosotros can select the Resource allotment view instead of Summary.

Allocations in heap allocations results

This view gives us a list of functions and memory allocations related to them. We can immediately see grow and createSomeNodes standing out. When selecting grow we become a look at the associated object constructors beingness called by it. Nosotros notice (string) , HTMLDivElement and Text which past now nosotros already know are the constructors of the objects being leaked.

The combination of these tools can help greatly in finding leaks. Play with them. Do dissimilar profiling runs in your production sites (ideally with not-minimized or obfuscated code). See if y'all can find leaks or objects that are retained more than they should (hint: these are harder to find).

To use this characteristic go to Dev Tools -> Settings and enable "record heap allocation stack traces". It is necessary to practice this before taking the recording.

Further reading

  • Retentiveness Direction - Mozilla Developer Network
  • JScript Memory Leaks - Douglas Crockford (old, in relation to Internet Explorer vi leaks)
  • JavaScript Memory Profiling - Chrome Developer Docs
  • Memory Diagnosis - Google Developers
  • An Interesting Kind of JavaScript Memory Leak - Shooting star blog
  • Grokking V8 closures

Aside: using Auth0'southward JavaScript Library to Authenticate

At Auth0 we use JavaScript heavily. Using our authentication and authorization server from your JavaScript spider web apps is a piece of cake. Here's one simple example using ECMAScript 2015 features and the Auth0.js library.

This is the principal client-side script to cosign and authorize a user to admission an API. It also updates the DOM to show some user data.

                      const            auth0            =            new            window.auth0.WebAuth            (            {            clientID:            "YOUR-AUTH0-CLIENT-ID"            ,            domain:            "YOUR-AUTH0-DOMAIN"            ,            scope:            "openid email profile YOUR-Additional-SCOPES"            ,            audience:            "YOUR-API-AUDIENCES"            ,            // Come across https://auth0.com/docs/api-auth            responseType:            "token id_token"            ,            redirectUri:            "http://localhost:9000"            //YOUR-REDIRECT-URL            }            )            ;            office            logout            (            )            {            localStorage.            removeItem            (            'id_token'            )            ;            localStorage.            removeItem            (            'access_token'            )            ;            window.location.href            =            "/"            ;            }            part            showProfileInfo            (            profile            )            {            var            btnLogin            =            document.            getElementById            (            'btn-login'            )            ;            var            btnLogout            =            document.            getElementById            (            'btn-logout'            )            ;            var            avatar            =            document.            getElementById            (            'avatar'            )            ;            certificate.            getElementById            (            'nickname'            )            .textContent            =            profile.nickname;            btnLogin.way.display            =            "none"            ;            avatar.src            =            profile.picture;            avatar.style.display            =            "block"            ;            btnLogout.style.brandish            =            "cake"            ;            }            part            retrieveProfile            (            )            {            var            idToken            =            localStorage.            getItem            (            'id_token'            )            ;            if            (idToken)            {            try            {            const            profile            =            jwt_decode            (idToken)            ;            showProfileInfo            (profile)            ;            }            catch            (err)            {            alert            (            'There was an error getting the contour: '            +            err.bulletin)            ;            }            }            }            auth0.            parseHash            (window.location.hash,            (            err,              upshot            )            =>            {            if            (err            ||            !event)            {            // Handle error            return            ;            }            // Yous can use the ID token to get user information in the frontend.            localStorage.            setItem            (            'id_token'            ,            result.idToken)            ;            // You can use this token to collaborate with server-side APIs.            localStorage.            setItem            (            'access_token'            ,            upshot.accessToken)            ;            retrieveProfile            (            )            ;            }            )            ;            function            afterLoad            (            )            {            // buttons            var            btnLogin            =            document.            getElementById            (            'btn-login'            )            ;            var            btnLogout            =            document.            getElementById            (            'btn-logout'            )            ;            btnLogin.            addEventListener            (            'click'            ,            function            (            )            {            auth0.            qualify            (            )            ;            }            )            ;            btnLogout.            addEventListener            (            'click'            ,            function            (            )            {            logout            (            )            ;            }            )            ;            retrieveProfile            (            )            ;            }            window.            addEventListener            (            'load'            ,            afterLoad)            ;                  

Get the fully working example and signup for a free account to endeavor it yourself!

Conclusion

Retentiveness leaks can and do happen in garbage collected languages such as JavaScript. These can become unnoticed for some time, and eventually they volition wreak havoc. For this reason, memory profiling tools are essential for finding retentivity leaks. Profiling runs should be part of the development cycle, particularly for mid or big-sized applications. Start doing this to give your users the all-time possible experience. Hack on!

harrisonmosters.blogspot.com

Source: https://auth0.com/blog/four-types-of-leaks-in-your-javascript-code-and-how-to-get-rid-of-them/

0 Response to "How Do You Know if You Have Memory Leaks"

Postar um comentário

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel