├── README.md └── docs ├── Callbacks ├── EndpointStatus.md └── README.md ├── Closures ├── MeteorJSExample.md ├── NotUsedCode.md ├── Promise.race.md └── README.md ├── DOMReferences ├── README.md └── SimpleStaticMemoryLeak.md ├── GlobalVariables ├── CacheService.md └── README.md ├── JQuery ├── PrevObject.md └── README.md └── NotKilledTimers ├── GonzaloRuizDeVillaModifiedExample.md └── README.md /README.md: -------------------------------------------------------------------------------- 1 | Javascript Memory Leaks Page 2 | ============================ 3 | 4 | ## Examples 5 | 6 | * [Global variables](./docs/GlobalVariables/README.md) 7 | * [Cache service](./docs/GlobalVariables/CacheService.md) 8 | * [Callbacks](./docs/Callbacks/README.md) 9 | * [Endpoint status](./docs/Callbacks/EndpointStatus.md) 10 | * [Not killed timers](./docs/NotKilledTimers/README.md) 11 | * [Gonzalo Ruiz de Villa Modified Example](./docs/NotKilledTimers/GonzaloRuizDeVillaModifiedExample.md) 12 | * [Closures](./docs/Closures/README.md) 13 | * [Not used code](./docs/Closures/NotUsedCode.md) 14 | * [MeteorJS Example](./docs/Closures/MeteorJSExample.md) 15 | * [Promise.race Example](./docs/Closures/Promise.race.md) 16 | * [DOM References](./docs/DOMReferences/README.md) 17 | * [Simple static memory leak](./docs/DOMReferences/SimpleStaticMemoryLeak.md) 18 | * [jQuery](./docs/JQuery/README.md) 19 | 20 | ## Resources 21 | 22 | * [Memory leaks in Javascript](https://slides.com/xufocoder/memory-leaks-in-the-javascript-4) 23 | * [Memory leak patterns in JavaScript](https://www.ibm.com/developerworks/web/library/wa-memleak/wa-memleak-pdf.pdf) 24 | * [Memory Analysis 101](https://developer.chrome.com/devtools/docs/memory-analysis-101) 25 | * [Fixing Memory Leaks in AngularJS and other JavaScript Applications](https://www.codeproject.com/Articles/882966/Fixing-Memory-Leaks-in-AngularJS-and-other-JavaScr) 26 | * [The Breakpoint Ep. 8: Memory Profiling with Chrome DevTools](https://www.youtube.com/watch?v=L3ugr9BJqIs) 27 | * [Effectively Managing Memory at Gmail scale](https://www.html5rocks.com/en/tutorials/memory/effectivemanagement/) 28 | * [Memory leak issues on kentcdodds.com](https://www.youtube.com/watch?v=I940kjuhaJI) 29 | 30 | -------------------------------------------------------------------------------- /docs/Callbacks/EndpointStatus.md: -------------------------------------------------------------------------------- 1 | ## Endpoint status 2 | 3 | **What is a memory leak:** interval create event listeners for every tick 4 | 5 | ```js 6 | function checkStatus() { 7 | fetch('/endpoint').then(function(response) { 8 | var container = document.getElementById("container"); 9 | container.innerHTML = response.status; 10 | 11 | container.addEventListener("mouseenter", function mouseenter() { 12 | container.innerHTML = response.statusText; 13 | }); 14 | container.addEventListener("mouseout", function mouseout() { 15 | container.innerHTML = response.status; 16 | }); 17 | }) 18 | } 19 | 20 | setInterval(checkStatus, 100); 21 | ``` 22 | 23 | **How to fix:** extract creating event listeners 24 | 25 | ```js 26 | var container = document.getElementById("container"); 27 | var status = { 28 | code: null, 29 | text: null 30 | } 31 | 32 | container.addEventListener("mouseenter", function() { 33 | container.innerHTML = status.code; 34 | }); 35 | 36 | container.addEventListener("mouseout", function() { 37 | container.innerHTML = status.text; 38 | }); 39 | 40 | function processResponse(response) { 41 | status.code = response.status 42 | status.text = response.statusText 43 | } 44 | 45 | function checkStatus() { 46 | fetch('/endpoint').then(processResponse) 47 | } 48 | 49 | setInterval(checkStatus, 100) 50 | ``` 51 | 52 | -------------------------------------------------------------------------------- /docs/Callbacks/README.md: -------------------------------------------------------------------------------- 1 | ## Callbacks 2 | 3 | * [Endpoint status](./EndpointStatus.md) 4 | -------------------------------------------------------------------------------- /docs/Closures/MeteorJSExample.md: -------------------------------------------------------------------------------- 1 | ## MeteorJS Example 2 | 3 | [Source of example](https://blog.meteor.com/an-interesting-kind-of-javascript-memory-leak-8b47d2e7f156) 4 | 5 | ```js 6 | var theThing = null 7 | var replaceThing = function () { 8 | var originalThing = theThing 9 | var unused = function () { 10 | if (originalThing) 11 | console.log("hi") 12 | } 13 | theThing = { 14 | longStr: new Array(1000000).join('*'), 15 | someMethod: function () { 16 | console.log(someMessage) 17 | } 18 | } 19 | } 20 | setInterval(replaceThing, 1000) 21 | ``` 22 | 23 | **How to fix** 24 | 25 | ```js 26 | 27 | var theThing = null 28 | var replaceThing = function () { 29 | var originalThing = theThing 30 | var unused = function () { 31 | if (originalThing) 32 | console.log("hi") 33 | } 34 | theThing = { 35 | longStr: new Array(1000000).join('*'), 36 | someMethod: function () { 37 | console.log(someMessage) 38 | } 39 | } 40 | originalThing = null; 41 | } 42 | setInterval(replaceThing, 1000) 43 | ``` 44 | 45 | -------------------------------------------------------------------------------- /docs/Closures/NotUsedCode.md: -------------------------------------------------------------------------------- 1 | ## Not used code 2 | 3 | [Source of example](https://bugs.chromium.org/p/chromium/issues/detail?id=315190) 4 | 5 | Memory can't be released because it can't be allocated before 6 | 7 | ```js 8 | function f() { 9 | var some = []; 10 | while(some.length < 1e6) { 11 | some.push(some.length); 12 | } 13 | function unused() { some; } //causes massive memory leak 14 | return function() {}; 15 | } 16 | 17 | var a = []; 18 | var interval = setInterval(function() { 19 | var len = a.push(f()); 20 | document.getElementById('count').innerHTML = len.toString(); 21 | if (len >= 500) { 22 | clearInterval(interval); 23 | } 24 | }, 10); 25 | ``` 26 | 27 | **How to fix:** investigate and refactor code, try to remove not used functional 28 | 29 | ```js 30 | function f() { 31 | return function() {}; 32 | } 33 | 34 | var a = []; 35 | var interval = setInterval(function() { 36 | var len = a.push(f()); 37 | document.getElementById('count').innerHTML = len.toString(); 38 | if (len >= 500) { 39 | clearInterval(interval); 40 | } 41 | }, 10); 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/Closures/Promise.race.md: -------------------------------------------------------------------------------- 1 | ## Not used code 2 | 3 | [Details](https://github.com/nodejs/node/issues/17469#issuecomment-685216777) 4 | 5 | When you call Promise.race with a long-running promise, the resolved value of the returned promise gets retained for as long as each of its promises do not settle. 6 | Example code to demonstrate: 7 | 8 | ```js 9 | async function randomString(length) { 10 | await new Promise((resolve) => setTimeout(resolve, 1)); 11 | let result = ""; 12 | const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 13 | for (let i = 0; i < length; i++) { 14 | result += characters.charAt(Math.floor(Math.random() * characters.length)); 15 | } 16 | return result; 17 | } 18 | 19 | (async function main() { 20 | let i = 0; 21 | const pending = new Promise(() => {}); 22 | while (true) { 23 | await Promise.race([pending, randomString(10000)]); 24 | if (i++ % 1000 === 0) { 25 | const usage = process.memoryUsage(); 26 | const rss = Math.round(usage.rss / (1024 ** 2) * 100) / 100; 27 | const heapUsed = Math.round(usage.heapUsed / (1024 ** 2) * 100) / 100; 28 | console.log(`RSS: ${rss} MiB, Heap Used: ${heapUsed} MiB`); 29 | } 30 | } 31 | })(); 32 | ``` 33 | In this example, we pass a large random string along with a non-settling promise to Promise.race, and the result is that every single one of these strings is retained. 34 | 35 | **How to fix:** 36 | Avoid Promise.race. Replacing your "await Promise.race" expressions with "await new Promise" expressions, where the newly constructed Promise sets a function variable in the outer scope. 37 | 38 | ```js 39 | (async function main() { 40 | let i = 0; 41 | // These functions are set in a promise constructor later 42 | let resolve; 43 | let reject; 44 | 45 | const pending = new Promise(() => {}); 46 | pending.then((value) => resolve(value), (err) => reject(err)); 47 | 48 | while (true) { 49 | randomString(10000).then((value) => resolve(value), (err) => reject(err)); 50 | // This is the await call which replaces the `await Promise.race` expression in the leaking example. 51 | // It sets callbacks for our promises 52 | await new Promise((resolve1, reject1) => { 53 | resolve = resolve1; 54 | reject = reject1; 55 | }); 56 | if (i++ % 1000 === 0) { 57 | const usage = process.memoryUsage(); 58 | const rss = Math.round(usage.rss / (1024 ** 2) * 100) / 100; 59 | const heapUsed = Math.round(usage.heapUsed / (1024 ** 2) * 100) / 100; 60 | console.log(`RSS: ${rss} MiB, Heap Used: ${heapUsed} MiB`); 61 | } 62 | } 63 | })(); 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/Closures/README.md: -------------------------------------------------------------------------------- 1 | ## Closures 2 | 3 | * [Not used code](./NotUsedCode.md) 4 | * [MeteorJS Example](./MeteorJSExample.md) 5 | * [Promise.race Example](./Promise.race.md) 6 | -------------------------------------------------------------------------------- /docs/DOMReferences/README.md: -------------------------------------------------------------------------------- 1 | ## DOM References 2 | 3 | * [Simple static memory leak](./SimpleStaticMemoryLeak.md) 4 | -------------------------------------------------------------------------------- /docs/DOMReferences/SimpleStaticMemoryLeak.md: -------------------------------------------------------------------------------- 1 | ## Simple static memory leak 2 | 3 | ```html 4 |
5 |
6 | 7 |
8 |
9 | ``` 10 | 11 | ```js 12 | var elements = { 13 | container: document.querySelector('#container'), 14 | form: document.querySelector('form'), 15 | submit: document.querySelector('[type="submit"]') 16 | } 17 | 18 | elements.form.addEventListener('submit', function(e) { 19 | e.preventDefault() 20 | elements.container.innerHTML = 'Ops..' 21 | }) 22 | ``` 23 | 24 | **How to fix** 25 | 26 | ```js 27 | var elements = { 28 | container: document.querySelector('#container'), 29 | form: document.querySelector('form'), 30 | submit: document.querySelector('[type="submit"]') 31 | } 32 | 33 | function processSubmit(e) { 34 | e.preventDefault() 35 | 36 | elements.form.removeEventListener('submit', processSubmit) 37 | elements.container.innerHTML = 'Ops..' 38 | 39 | elements = { 40 | container: document.querySelector('#container') 41 | } 42 | } 43 | 44 | elements.form.addEventListener('submit', processSubmit) 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/GlobalVariables/CacheService.md: -------------------------------------------------------------------------------- 1 | ## Cache service 2 | 3 | **What is a memory leak:** after removing `CacheService` instance memory must be released 4 | 5 | ```js 6 | (function(window) { 7 | cache = [] 8 | 9 | window.CacheService = function() { 10 | return { 11 | cache: function(value) { 12 | cache.push(new Array(1000).join('*')) 13 | cache.push(value) 14 | } 15 | } 16 | } 17 | })(window) 18 | 19 | var service = new window.CacheService() 20 | 21 | for (let i=0; i < 99999; i++) { 22 | service.cache(i) 23 | } 24 | 25 | service = null 26 | ``` 27 | 28 | **How to fix:** move `cache` variable to `CacheService` scope like the followings: 29 | 30 | ```js 31 | (function(window) { 32 | window.CacheService = function() { 33 | var cache = [] 34 | 35 | return { 36 | cache: function(value) { 37 | cache.push(new Array(1000).join('*')) 38 | cache.push(value) 39 | } 40 | } 41 | } 42 | })(window) 43 | 44 | var service = new window.CacheService() 45 | 46 | for (let i=0; i < 99999; i++) { 47 | service.cache(i) 48 | } 49 | 50 | service = null 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/GlobalVariables/README.md: -------------------------------------------------------------------------------- 1 | ## Global variables 2 | 3 | Scripts create objects into global scope: 4 | 5 | * [Cache service](./CacheService.md) 6 | -------------------------------------------------------------------------------- /docs/JQuery/PrevObject.md: -------------------------------------------------------------------------------- 1 | ## prevObject 2 | 3 | **What is a memory leak:** jQuery supports chaining/fluid style. In order to make it work it keeps references to other jQuery objects in a `.prevObject` property 4 | 5 | ```js 6 | const $leaky = $('
LEAK
').find('span').parent().empty(); // [
] 7 | 8 | // has been removed from the
and we expect it to be garbage collected 9 | 10 | // Oh no! It's still here! 11 | console.log( $leaky.prevObject ); // [ LEAK ] 12 | 13 | // And we can "end" the chaining and get it back even if it's already detached 14 | console.log( $leaky.end() ); // [ LEAK ] 15 | ``` 16 | 17 | **How to fix:** Delete the `.prevObject` or create a new jQuery object from this one 18 | 19 | ```js 20 | const $clean = $( 21 | $('
LEAK
').find('span').parent().empty() 22 | ); // [
] 23 | 24 | // has been removed from the
and we expect it to be garbage collected 25 | 26 | // Yup, there's no more references and it can be GC'ed. 27 | console.log( $clean.prevObject ); // undefined 28 | console.log( $clean.end() ); // [ /* an empty jQuery object */ ] 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/JQuery/README.md: -------------------------------------------------------------------------------- 1 | ## jQuery 2 | 3 | * [prevObject](./PrevObject.md) 4 | -------------------------------------------------------------------------------- /docs/NotKilledTimers/GonzaloRuizDeVillaModifiedExample.md: -------------------------------------------------------------------------------- 1 | ## Gonzalo Ruiz de Villa Modified Example 2 | 3 | [Source of example](http://slides.com/gruizdevilla/memory#/5/17) 4 | 5 | ```js 6 | var strangeObject = { 7 | storage: [], 8 | callAgain: function () { 9 | var ref = this 10 | ref.storage.push(new Array(1000000).join('*')) 11 | var val = setTimeout(function () { 12 | ref.callAgain() 13 | }, 50) 14 | } 15 | } 16 | 17 | strangeObject.callAgain() 18 | strangeObject = null 19 | ``` 20 | 21 | **How to fix:** use interval and allow to clean it 22 | 23 | ```js 24 | var strangeObject = { 25 | storage: [], 26 | startCallAgain: function() { 27 | this.interval = setInterval(this.tickCallAgain.bind(this), 50) 28 | }, 29 | tickCallAgain: function() { 30 | this.storage.push(new Array(1000000).join('*')) 31 | }, 32 | stopCallAgain: function() { 33 | if (this.interval) { 34 | clearInterval(this.interval) 35 | } 36 | } 37 | } 38 | 39 | strangeObject.startCallAgain() 40 | 41 | setTimeout(function() { 42 | strangeObject.stopCallAgain() 43 | strangeObject = null 44 | }, 5000) 45 | ``` 46 | 47 | -------------------------------------------------------------------------------- /docs/NotKilledTimers/README.md: -------------------------------------------------------------------------------- 1 | ## Not killed timers 2 | 3 | * [Gonzalo Ruiz de Villa Modified Example](./GonzaloRuizDeVillaModifiedExample.md) 4 | --------------------------------------------------------------------------------