Introduction to Memory Management in JavaScript

Can someone please explain this article in English?

  1. Why does the undefined “person” variable reference the aaliyah object?

  2. What does “messy closure” mean, and why is bigObj not cleared the way an internal variable normally would be when the function completes? In fact, why would it grow at all - wouldn’t it just be overwritten each time it’s called, if it wasn’t already deleted? Or, since it’s a constant, just resisted overwriting and stayed what it was from the first call?

  3. So should we never use setInterval indefinitely? Not that I’d usually want to, but what if there’s a situation where we actually do want something to run every 10 seconds for as long as the page is loaded? And again, why would that use a ton of memory - there’s only 1 variable, that’s presumably overwriting itself, always equaling either 0 or 1.

  4. Why is it a bad thing to store a variable inside the anonymous function that uses it instead of setting a variable that it has to store even when that function isn’t in use?

  5. What’s an object pool? I thought the point of this article was to keep variables local when possible so that the memory could be reused, but now it sounds like it actually wants variables to be global to reduce garbage collection?

Please drop us a link to the article so we can have a read.

Oops, sorry - forgot I hadn’t. Added the link above, but also: https://www.codecademy.com/courses/learn-intermediate-javascript/articles/javascript-intro-to-memory-management

A memory leak can happen when garbage collection fails to find an object that lost its connection to the root object, or when objects grow in size and are referenced by other objects. When this happens, it can be the source of slowdowns, crashes, and high latency in your code.

Your questions, and their answers center around the above. Start by postulating. Answer your own questions, as it were, intuitively, and then let us share the load, so to speak. Better we know how you understand this, before proceeding. Over to you…

Well… I can try my best, but I was really getting lost in the examples even though I thought I understood that key concept. So basically thinking out loud going through these again:

  1. My first thought was that this was just a typo, but when none of the later examples made sense to me either I thought maybe I was missing something. This would make sense to me if the object aaliyah was passed into the code - not in creating a memory leak, but in being a potential bug caused by the way memory works - because the function would then change the aaliyah object as well as assigning it to the new sara variable. But I don’t see how passing an undefined variable person in would overwrite a different previously defined variable. Obviously you can have more than one object, so why would the new sara object overwrite the first?

  2. My guess when I saw the term “messy closure” was maybe some sort of function that never actually completes. But that doesn’t seem to be what’s happening. bigObjMaker is a relatively simple function that creates, logs, and returns an object. It does seem odd to me to log within the return statement, but I don’t see how that would prevent the function from completing. A return statement is a pretty definite completion, isn’t it?

bigMemoryUser is another instance of the same function. And then… I’m not familiar with fill and map, but bigMemoryUser is apparently being called 1000 times? Yes, that’s a lot of times to run it, but wouldn’t it just be one at a time, using the same amount of memory each time, and releasing it when it’s done? It’s not even saving the created objects anywhere, unless the map part is doing that. And since bigObj is created inside the function, isn’t it local? Shouldn’t it clear each time the function completes? Does setting it with const instead of let actually overwrite the location of it to make it global rather than local? I would have thought not…

The article says “the closure over the bigObj object keeps the memory in use” but if we’re not talking about closure of a function what are we referring to?

  1. The section on setInterval says it causes a memory leak because it hasn’t been assigned to a variable that will allow you to use clearInterval on it. But what I’m not following is why you need to. It’s running the function cb() once per second. Yes, this is going to fill up your stack and not allow much room for any further code to run, so I see it can be not best practice (especially that frequently), but if it’s the last thing you do and something you do want to run continually how is it specifically a memory leak? My understanding is that once per second, a function frame for cb() gets added to the stack. Each time, that function creates a local variable that it sets to 0, then logs and returns that variable plus 1 (so logs and returns 1). This time, it’s not even set with const - but it does still have that odd formation of including the logging in the return statement. Is it something about that that causes the memory leak? Again, I would have thought that this function would just be repeatedly running, completing, clearing its heap info, then the next one would run it again… and if not - if the repetition of the same function somehow doesn’t give it a chance to clear because it still references the same variable - then wouldn’t it just be overwriting rather than getting bigger?

  2. Now I’m lost because we seem to have turned to advocating for making variables global, which wouldn’t clear them. But I guess this is something about the function being anonymous? Does not naming your function in the stack confuse the algorithm about what it can clear from the heap?

  3. “Some aspects of your code, like high object churn, can cause more frequent garbage collection, which in turn can slow down your program. One solution to this is to use an object pool, which retains some memory as an unused object that you can recycle instead of allocating and garbage collecting memory.” So this does seem to have turned to advocating for global objects instead of local ones. Avoiding uncleared memory you don’t realize is uncleared by purposely not clearing it? Now the problem is the garbage collection itself, not what it misses? Or by “recycling” I suppose we could continually overwrite. But it seems bad practice to me to set a global variable generalParameter and then just keep reassigning it to different values in different functions. That seems prone to bugs and hard to troubleshoot, because when you read it later it will be hard to follow what its value is at any given moment.

This is not helped by the follow-up article, Debgging Memory Issues in Javascript, having multiple examples with the direction “Run this in your browser. What errors do you get?” or “Run this in your browser. How long until it crashes?” and I’m in Chrome with the console open in developer tools and it never crashes or produces any errors.

To your first question regarding 'undefined person', this error gets thrown…

let sarah = nameObjectModification(person, "Sarah");
                                   ^

ReferenceError: person is not defined

We’ve hit a wall, already. Will need to ask for this article to be reviewed.

Before going any further it’s important to understand what closure is. It’s not a finality, such as putting a past issue to bed. Think more in terms of enclosure. When a variable is declared in a function, and then referenced by a returned function or object, the return is said have closure over that variable. If the variable references an object or array, then that reference is kept alive in memory even after the function has terminated. One can envision how this will leak memory since the enclosing function is still stored.

We should be warned that I’m no expert here, and only marginally, if at all, more knowledgeable on the subject than you. An expert view is going to be needed if we are to get to the bottom of all this any time soon.

Going to request a review and try to pick this up again, presently.

1 Like

Thank you!

This was definitely a key point I was missing:

It’s not a finality, such as putting a past issue to bed. Think more in terms of enclosure […] If the [returned] variable references an object or array, then that reference is kept alive in memory even after the function has terminated.

I’m going to read through again with this in mind the next training time I get, as I think it will help move a lot of pieces into the right perspective. But I’d be happy to hear any other analysis you might have after the article is reviewed.

1 Like

Okay, take 2 after a reread, keeping in mind the definition of closure and the fact that if a function’s return includes a variable, that variable is then considered to have a reference outside the function.

  1. Since you also get an error, I’m going to assume my first impression of it being a typo was right and move on for now, unless we get clarification later in a rework of the article.

  2. I understand “messy closure” now, I think. The closure is “messy” when it spills out of the function, so to speak. Although don’t most functions return a variable? Otherwise you wouldn’t need a function; you could just set a string or number or whatever. But I suppose this is where it makes sense to modify the value passed into the function and return that - because then, you’re not sending any extraneous variables out. So rather than f(x){return x+y} you’d have f(x){x=x+y;return x}

  3. I think I’m getting confused at “If the handler is still active, anything it is referencing can’t be garbage collected.” What do we mean by the handler being active? Are the functions within timers and event listeners actually permanently active, rather than only being active when they’re called? If they’re not added to the stack when they’re called, what is the trigger that causes them to run then, and why would there be repeated instances?

Disregarding that sentence, my guess at what’s happening here is that it’s because the count variable is being created and returned within cb() so that it becomes the same problem as # 2.

  1. So is the issue here not actually the anonymousness of the function itself - though it says “Another scenario to watch out for is the existence of anonymous functions” - but that the large variable is referenced within it rather than being passed to it? Which…I guess is in part the fault of the annonymous function, because an annonymous function can basically only receive “this” whereas a defined function can use defined parameters? document.addEventListener(‘scroll’,cb(lotsOfMemory)) would then in theory not need to include the lotsOfMemory object and so would not waste memory by essentially saving it twice. Am I on the right track? But how exactly does closure work here - I would have thought that the reference within the function would expand closure, but I’m not sure how it instead duplicates it.

  2. I still don’t really have enough information to define “object pool” with anything other than a guess. Key global variables so that you don’t keep resetting and returning the same things in multiple functions?

A return value is what is sent back to the caller. It would only be a variable if the function returns a function that has closure over it. We’re mostly concerned with objects and arrays in this case because they are reference objects, not static values.

const foo = function (faz) {
  return x => x * faz;
}

Notice the above foo() is returning a function (a lambda, actually, but not called that in JS, instead, a concise body arrow function). That function has closure over the variable, faz.

console.log(foo(21)(2))    //  42

Notice we are able to invoke (call) the return with the argument 2.

Had we assigned the return, we would have a reusable function…

const timesByTwo = foo(2)
console.log(timesByTwo(21))    //  42

We will need to investigate this to determine whether the reference to the returned function is still locked up in memory. Like an object or array, a function is also returned by reference. One suspects this to be so. Until we can somehow delete the variable, timesByTwo, that reference is still alive.

Looking at the first example where we immediately invoked the return function, there is no reference to the function. In that case the foo() function can be garbage collected. Of course, the same would be true if our return function is itself inside a function. Once that function is terminated, all references would be gone.

f(x){return x+y} you’d have f(x){x=x+y;return x}

They are equivalent. There is no closure over any variable in either one. The return is a static value. The function can be garbage collected.

Will continue to read your questions. Above gives you some more food for thought.

Thank you for your patience in walking me through all this! This makes a lot of sense, and now I feel like I should have seen it before. Let me restate to make sure I get it:

It’s not any reference to a variable that extends the closure. Simple data - a string or a number - can just be returned as a value. But more complex information, like an array or object or function, will have closure over any variables it contains. Therefore it brings those variables with it in the return, and they can’t be garbage collected, not because of the return having closure as a rule but because of the specific type of return. So to go back to my attempt at simple examples:

function (x){return x + y;} is fine - it will just return the static result of x+y.
But
function(x){return {result : x + y};}
function(x){return x=>x+y;}
…and other such functions would have “messy closure” and keep the y variable (which I’m imagining the function defined before the return) in memory.

So coming back to the interval/event handler: Is the problem just that cb() inside the function returns another function? Or is there more going on here? I see that the anonymous function has closure over the functions/variables inside it, but that function’s not being returned, just called frequently. Unless the interval/event listener is also considered to have closure over the function, but then my understanding breaks when trying to follow how these can be repeated and how you’d ever write one that works properly.

My guess remains the same on “object pool.”

Thanks again!

1 Like

We’re inching along, here. Event listeners for all their tremendous value also come with a potentially high cost. They are expensive in terms of resources, both memory and time. As we continue to study this aspect of memory usage and management (what we can do, anyway) it falls in good stead to minimize the number of active listeners and/or time intervals; and, to turn them off immediately when no longer in use.

One way (the surest I know of) to minimize listeners is with event delegation. The higher we can let the event bubble up, the less listeners we need and the greater the coverage area of any one listener. We do this by grouping everything that is event driven under one parent (one listener) and then delegate the target event object to an appropriate handler. In other words, use control flow to direct which action should take place. The handler doesn’t perform the action but rather directs traffic to the attendant action.

At this point, having a surface understanding is better than none, albeit this subject will continue to surface as one gets into larger projects. Fortunately, in this day and age one seldom works alone in a bubble. No one is alone in building a large app or site. Unless one is the engineer at whose feet the memory management issue falls, we need only learn how to consider how our app utilizes memory. JS is pretty good at managing what it has to, and all we need to do is make sure we’re not pouring on too much of a load.

Asymptotic notation (Big-O) is a management tool that lets us know whether our algorithms are efficient, fast, and resource friendly. The less footprint we leave on memory, the less that can be leaked away. Our goal right now is to establish good practices, some of which center around what we’ve been discussing, thus far. Over time, one will find what concerns may arise will likely be fewer, and easier to address if our code is built upon suitable principles, already. All in good time.

Thank you again - but how do you “turn them off when no longer in use”? Isn’t the point of a listener that you don’t know when or if or how many times it will be used? It’s usually an on click or on submit; they’re generally never guaranteed to be “no longer in use.”

I’m also not sure I follow the idea of delegating to multiple handlers in order to combine event listeners. If I call different functions for different things, won’t those all, as functions, still get stored within the main listener function?

Their example is:
const lotsOfMemory = “Imagine this is a value that uses a lot of memory”

document.addEventListener(‘scroll’, function() {
cb(lotsOfMemory);
});

Maybe the closure doesn’t… I don’t really have the right word, but doesn’t layer?
Would this still enclose the lotsOfMemory variable?
const lotsOfMemory = “Imagine this is a value that uses a lot of memory”
function cb(){
console.log(lotsOfMemory);
}
document.addEventListener(‘scroll’, cb);

…or would it only include it if it returned lotsOfMemory, and lotsOfMemory was an object not a string?

Also - happy holidays! Not sure if you’ll be back online before Christmas/New Years, but I will be going offline soon. I appreciate all your help!

If we use a time interval during a set of operations or repeated procedures, the timer should only be running during that process. When the process is complete, we clear the interval to turn off the timer, else it just keeps running.

Event listeners are more ubiquitous in terms of live page elements. In this case we would remove the listener if we no longer wish a page element to be active. This would be done in the handler, or on the return from the handler.

When a function returns an object, the reference is still alive in the function so it cannot be cleared. That is why I suggested only implementing this from inside another function, so the reference does die and all the functions can be cleared. Calling a factory from inside a function is better in terms of lost memory than calling it from global scope.

As for delegation, consider a key pad with ‘+’, ‘-’, ‘*’, ‘/’, ‘%’, ‘//’, ‘**’, all the digits 0-9, a dot and an equals sign. Do we put a listener on each button? No. That would be the expensive (nineteen listeners) and very inefficient way to do it. Instead, house all the buttons in a single container and register the listener on it. Its handler will check the target and match it to the correct function.

After the holidays we can explore this further. If you have some time, look up some articles on event bubbling and delegation. This is an old concept so there should be lots of good reads. SitePoint is one place to look for this topic, among the many others. MDN’s wiki likely has articles on the subject, as well.

Happy Holidays!

I just have a question about the following code that is found in this article:

let object = new Object();
let object2 = object;
object.greeting = “Hello, world”;

console.log(object2); // Hello, world

Question: I don’t understand how ‘console.log(object2)’ prints ‘Hello, world’. My understanding is that we should be console logging object2.greeting instead of just object2 in order to get ‘Hello, world’ to print.

Please post a link to the article in a reply. That last line should output the object literal…

{greeting: 'Hello world'}

The point of the article from what we can see, thus far, is that both variables refer to the same object. object2 is not a copy of object.

thank you MTF,

I agree, I also think that it should print the following:’{greeting: ‘Hello world’}’. however in the article it shows 'Hello, world" only.

here is the link to the article: https://www.codecademy.com/courses/learn-intermediate-javascript/articles/javascript-intro-to-memory-management

i also attached a screenshot of the code i’m referring to.

1 Like