Memory Life Cycle in JS(Heap, Stack, Call Stack, String Pool)
- What is Stack and Heap Memory and how does it work?
- What is the difference between Call Stack and Stack?
- What are the main Differences of Stack and Heap?
- Where and how are strings stored in memory?
- How is the data stored and what are the limits?
- How are Primitive and Non-Primitive variables stored in memory?
- What is Unique Identifier?
- How is memory management during Deep and Shallow Copy?
- Why are const variables sometimes mutable and sometimes immutable?
and so on, I will try to answer such questions in this article.
When you create variables in JS, the JS Engine automatically places them in memory. We will elaborate on those memory areas below.
Let’s start…
Stack Memory
Ram is a limited memory area that stores data in a Linear data structure .
It works with the LIFO ( Last In First Out ) concept. That is, the last data written to the Stack is the first to be deleted.
We can imagine the overall structure as plates stacked on top of each other.
When the code is executed:
If variable is “Non-Primitive type“ (Object, Array, Function,…), The “name” and “address” of variable are stored in Stack, and “value” and “address” are stored in Heap memory.
If variable is “Primitive type “, both Name and Value are stored only in Stack.
When the scope containing the variable finishes executing, everything associated with it is immediately removed from the Stack.
If it is a non-primitive type, the value still remains in the Heap. It is then cleaned by the Garbage Collector .
To understand how primitive type variables are managed in memory, let’s create a primitive type variable, then update it, and see what happens on the Stack:
let myNumber = 23
let newVar = myNumber
myNumber = myNumber + 1
Let’s look at another example:
let myString = 'abc'
myString = myString + 'd'
As a result, a picture like the one below will appear:
Since the stack works with the LIFO concept, the last entered myString variable will start to be deleted first when the block( {} ) in which it was declared finishes.
Heap Memory
It is an unlimited memory space that stores data in a Hierarchical data structure .
Stores the “value and address” of variables declared in non-primitive type.
Memory allocation is very difficult compared to Stack
Each data written to heap memory is considered global and can be referenced from anywhere.
let myArray = []
Let’s try to understand what happens in memory in this case:
- First, a “ Unique Identifier” is created for myArray
- At runtime , the “name and address” of the variable are placed in the Stack
- The value of the variable at that address is written to Heap .
- Then the address on the Stack is copied to the Heap .
Unique Identifier - address that indicates the location of the variable in memory .
myArray.push(“First”)
myArray.push(“Second”)
Example: In the following example, when if{} finishes processing, p2 on the stack is already deleted, but its counter part on the heap still remains.
And we won’t be able to reuse that data in the heap because it doesn’t have a counter part in the stack.
Because, to have access to any data in the Heap, we need the address of that data in the Stack.
As a result, the unused variables left in the Heap will become unnecessary Garbage after a while.
In high-level languages , the Garbage Collector automatically finds and deletes data from the Heap whose addresses are not in the Stack after a certain period of time.
Since there is no Garbage Collector in low level ( C, C++ ) languages, the programmer does it manually .
Cont keyword
As far as we know, it is not possible to write a new value for the 2nd time to the variable declared with const.
But if the variable is of Non-Primitive type, then it is not considered as Immutable data, and therefore we can change the item or property of its value.
What is the reason for this?
The reason is related to Memory Allocation .
Variables of primitive data type are considered Immutable data because they are stored entirely on the stack.
If we declare a variable with const, we prevent a new line from falling into the Stack , which is a static memory .
importantID = 100
// TypeError: Assignment to constant variable
But if we create a variable in Non-Primitive type, its value goes to the Heap, which is a dynamic memory, so it is possible to change its value.
Copy : Shallow vs Deep
There are 2 popular ways to copy: Shallow , Deep .
The main difference is that when we make a Shallow copy, we update the value of that variable in the Heap. In Deep copy, a new place is reserved for that variable in the Heap.
Therefore, the equality returns true because the Address and Value in the Heap of n3 and n4 are the same during the comparison.
For n3 and n5 , the opposite happens. When we say n3.slice(), we create n5 with the same value, and the new Address and Value are already written to n5’s Heap.
Key Differences between Stack and Heap
- Type of Data Structures → Stack “Linear”, Heap isə “Hierarchical ”data structure a malikdir.
- Speed → Stack is Faster than Heap
- Limit of Space Size → Stack has certain limit depending on OS but Heap has no limit .
- Memory Allocation → Data is placed in Stack with LIFO concept , and in Heap in random order.
- Main Issue → Stack’s main problem is lack of memory , and Heap’s is memory allocation
Call Stack is not same with Stack
One of the things I confused myself at first was between CallStack and Stack, because both are very similar and both work with LIFO concept.
First, let’s understand what the call-stack is:
Call Stack is a concept created to trace functions.
- Stores data of “ Invoked functions “.
- It works with the “ LIFO “ concept (Last In First Out), that is, the last function that enters the Call Stack is executed first.
- Since JS is “ Single Thread “, only one function is active at the same time. That is, functions run sequentially, not in parallel.
- Each element added is called a “ Stack Frame ”. Stack-traces that we use when an error occurs or when debugging all come from the Call Stack.
The stack is the memory in which the data of the variables is stored ( name and value if Primitive, name and address if Non-Primitive ).
NOTE: For more information about Call-Stack, you can visit this link
String Pool (Memory space)
Allocating strings in memory happens differently than we normally think.
So the values of String type variables are always stored in Heep Memory.
If it is created with new String(“ ”), it falls into the Heap like normal objects.
But if it is written in the form of “let a = ”” , then this value falls into the String Pool located in the Heap memory .
In the example below I have shown what I mean visually.
Thanks for your attention. I hope it was helpful…