Does JavaScript Use Garbage Collection? Improving Memory Efficiency
If you found yourself in the situation where a browser has been shut down unexpectedly, you may have wondered the reasons behind it.
In these scenarios, it is common for browsers to outright freeze or stop functioning altogether. It would make sense to attribute these issues to errors in JavaScript.
And for the most part, this is correct. Ther are a handful of ways of which JavaScript can exhaust the limit of a browser’s memory capacity.
These are often called memory leaks, and can significantly impact a user’s browsing experience.
One of the most common methods for a programming language to combat this state is to utilize garbage collection.
In short, a garbage collection is a form of automatic memory management. Its reclaims memory occupied by objects that are no longer in use in the execution of a program.
So, with that understood, does JavaScript use Garbage Collection?
JavaScript uses a method of garbage collection periodically when there are no remaining references to an unused object or variable. This process is completely automated and helps to ensure that a browser doesn’t overload and get out of memory.
Typically, this collection process follows these steps:
- Allocation of memory space required for the program
- The processing and execution of some code
- The freeing up of memory space from variables and objects that are no longer used
In order to detect which objects require to be removed, two main algorithms are used. This is called the Mark-and-sweep algorithm.
There are two phases in this algorithms lifecycle:
Mark Phase
This phase scans various objects and variables and ensures that they are referenced by something else in the program. If no reference point exists, delete the object from memory.
This phase is essentially scanning for all possible objects that are linked to each other. The compiler will traverse through all reachable objects (imagine a tree of nodes) and will stop when all reachable nodes have been visited.
Sweep Phase
Clear the heap memory for all the unreachable objects.
Any objects whose value is equal to false are cleared from the heap memory.
So, even though the process of garbage collection happens automatically, this doesn’t mean that a JavaScript developer doesn’t have to worry about it.
In fact, ignoring it can be detrimental to a program executing, let’s explore the topic of memory management further
What is memory leak JavaScript?
So while we’ve established the role of the garbage collector for preventing memory leaks. What exactly constitutes a memory leak?
In simple terms, a memory leak is data that the compiler has forgotten to use. It can be defined as memory that is no longer required by the application anymore but has not been released to free up the memory capacity.
When you create objects and variables the compiler has to consume some of its memory in order to keep track of this information.
As we noted earlier, the garbage collector will have the awareness to figure our when a variable is no longer required for the program to execute further.
A memory leak will occur when the program no longer needs a created variable but the run time environment (browser) thinks you do.
This causes a conflict between the run time environment and the execution of your code. In comparison to a typical runtime error, memory leaks often go unnoticed, in terms of error visibility.
This is because they aren’t actually caused by explicitly invalid code but rather a logical defect in how the code is structured.
Eventually, this leads to sluggish performance of the application and quite frequently will lead to an outright crash or freeze of the browser.
This is quite different to a explicit runtime error in your code, as the warnings of a leak can often go unrecognized.
The main cause for memory leaks in JavaScript is unwanted variable references. However, that is a somewhat oversimplification of possible memory leak scenarios.
Let’s analyze some frequently encountered situations where JavaScript experiences memory leaks.
Infinite Loops
Perhaps the most common culprit of memory leaks, infinite loops frequently cause browser crashes and freezes during user interaction.
To test this out try putting the following code in your developer console debugging tools:
while (true) { //your code }
The execution of this code will cause the browser to freeze. However, this snippet is clearly set up to cause a memory leak and unlikely to be found in typical production code.
It’s far more likely that a forgotten timer will create this scenario and the use of setInterval
is typically behind many of these occurrences.
Let take the following timeout for example:
var counter = 10; setInterval(function(){ console.log(counter); counter-- if (counter === 0) { console.log("I am running!"); } }, 1000);
At first glance, you’d be forgiven to assume that this loop output stops after 10 times. However, setInterval
doesn’t do much more than loop through a specified function.
By itself, it has no stop clause which can be overlooked when reaching for this JavaScript timeout utility.
When combined with clearInterval
, we can stop the timeout happening. It takes one parameter that specifies the function you want to clear.
var counter = 10; var runningFunction = setInterval(function(){ console.log(counter); counter-- if (counter === 0) { console.log("I am running!"); clearInterval(runningFunction); } }, 1000);
Adding clearInterval
where setInterval
is used can stop unnecessary memory leaks in your code.
Closures
As you may well know, closures are a core pillar of JavaScript fundamentals. Closures are often referred to as function that returns a function.
The main idea being that an inner function has access to the outer functions variables.
This means from the perspective of the inner function, the variables of the outer function are in scope, in otherwords they are accessible from within the inner function.
Let’s demonstrate this in an example:
function buildName(name) { var greeting = "Hello, " + name + "!"; var sayHello = function() { var welcome = greeting + " Welcome!"; console.log(greeting); }; return sayName; }
- This is a simple demonstration of a closure at work
buildName
creates a variable in its local scopesayHello
exists as the function in the inner scope (insidesayHello
).sayHello
still has access to the greeting variable even though it was created in a different scope.
Closures are fantastically efficient when trying to write code that is recursive as well as code that doesn’t repeat itself.
While this is great, how can closures cause memory leaks?
Well, a memory leak occurs in a closure if one of the variables declared in the outer most function becomes available for the inner nested function.
This reference in memory has to exist in order for the inner nested function to have access to those variables. Because of constant reference, the variables from the outer function can’t be garbage collected.
The reference to the variables will continue consuming memory of the JavaScript run-time engine and may result in memory leaks.
Setting State
When working with Reactjs, I have repeatedly come across memory leaks misusing this.setState()
.
The following warning rears its head in my console logs.
warning.js:33 Warning: Can’t call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
This happens a lot more than I’d care to admit but thankfully it’s quite a straightforward problem to trace.
Typically, when setState
is invoked, the component is rerendered as well as all the logic inside the render function.
If this logic happens to call this.setState()
again, the result is endless cycling of re-rendering.
This is a substantial use of memory in the JavaScript compiler and tends to crash the browser promptly. To avoid this, put your setState call behind a condition or a flag that dictates when the rerendering should stop.
state = { isMounted: false } componentDidMount() { this.setState({isMounted: true}) } componentWillUnmount(){ this.state.isMounted = false }
And inside your render function:
if (this.state.isMounted) { this.setState({ loading: false, isMounted:false }); }
Perfect, the rendering will only happen once when the component first mounts.
Accidental global variables
An easy memory leak related trap to fall into is accidentally keeping global variables in your code.
Here is an example of a global variable being created:
var createdVariable = "This is a global variable"; function myFunction() { // the variablecreatedVariable is accessible here inside the myFunction() as well }
Any variable created at the top scope level of the document becomes a global variable.
In the above example, createdVariable
been created outside the scope of myFunction
and since there are no other local scopes, globalVariable
resides at the top level.
Global variables are not removed by garbage collectors. There should always be careful consideration when using global variables in a codebase. Global variables live in the outermost scope of the JavaScript document.
Think of the window object in the browser, this is essentially the highest tier of scope in which our JavaScript resides.
You can try this by opening up google developer tools and typing this
into the console.
The window object is given back to us here, indicating that the current scope is, in fact, the global scope.
Any variables declared globally can be accessed by all scripts, libraries, and functions in the JavaScript document. This is one of the main reasons why it isn’t garbage collected.
The reliance of other scripts on global variables is a practical enough reason for the garbage collector to hold off on removing these references.
But of course, there are situations where accidental global variables are created. Let’s look at a few culprits:
Use strict
The implementation of use strict, can prevent unwanted global variable. This is a rather handy utility to use.
For example:
'use strict' var myData = 'Hello'; yourData = 'Goodbye';
Without using strict here, the yourData
variable would be hoisted up to the global scope. However, using it will throw a reference error that can avoid polluting the global scope with variable references that will never be garbage collected.
Block scope
As is now commonplace in the modern JavaScript world, the use of let instead of var to declare a variable has become the standard.
The use of let enforced the variable to be block scoped. This means that it can only be accessed within the confines of the block, statement or function where it is declared. As a result, the variable will not be referenced on the global scope.
Extensions
While most highly rated browser extensions are usually optimised to work alongside the browser. There are plenty of cases where memory leaks occur due to failure to release resources.
This type of memory leak is called Zombie Compartments. And is commonly experienced in an event such as closing a window, a page unloading or when enabling or disabling an extension.
This is documented very thoroughly in the MDN docs.
Additionally, older browsers such as IE5+ were notorious for creating memory leaks. As they have an inferior garbage collection algorithm in comparison to today’s modern browsers.
Conclusion
Dealing with memory leaks can be a pain, especially as they comprise the performance of an application. However, it must be acknowledged that the process of garbage collection by modern JavaScript runtime engines is a very efficient method of clearing up space.
And while it doesn’t get it right 100% of the time, it is often not without a valid reason. And is most likely an error by the person that created the program.
In particular, not removing any variables from the global scope seems like a logical decision, as multiple libraries and files could rely on the same variable reference.
Avoiding memory leaks should be the prerogative of any keen programmer striving for efficiency and is just one of the many niche subcategories of JavaScript optimization.