2

I've got a couple of questions about memory allocation in JavaScript

As far as I know, JavaScript primitives are immutable and stored in the stack. If we change the value of a primitive or if we assign a new variable to the old variable, it creates a new memory location for each case.

let x = 2 
let y = x
x = 3
console.log(y) // 2

  1. When we run a large loop just as the example bellow, 99999999 times 8bytes needed in stack to allocate space for i. So, why the stack is not overflown?
    for(let i = 0; i<99999999; i++){
       let x = i
    }
  1. If millions of objects are created simultaneously (in a real world app), is the stack enough to hold references to all the objects in the heap?
5
  • 5
    Your example does not use the stack.. Commented Sep 5, 2022 at 14:50
  • 1
    Your variable x goes out of the {} bracketed scope, on every "for" iteration. Your let-statement is only local, inside the loop. There will be no (or little, temporary) memory consumption, all instances of x will be trashed by the JS garbage collector. Commented Sep 5, 2022 at 14:54
  • "JavaScript primitives are immutable and stored in the stack" - no. Commented Sep 5, 2022 at 15:19
  • @Keith Thanks for the answer. So, does this mean that usually the memory allocation for everything (primitives and objects) take place in the heap? Commented Sep 9, 2022 at 21:22
  • I'm sure the stack is used plenty of times inside the JS engine, but generally not something you need to be concerned about, the only time you would need to think about it is when doing recursion. Commented Sep 11, 2022 at 9:40

3 Answers 3

2

(V8 developer here.)

  1. When we run a large loop just as the example bellow, 99999999 times 8bytes needed in stack to allocate space for i. So, why the stack is not overflown?

The premise is incorrect. There's only one stack slot for i. Each iteration of the loop reuses it. Whether this loop runs once, or twice, or 100 times, or 9999... times therefore doesn't change how much stack space it needs.

  1. If millions of objects are created simultaneously (in a real world app), is the stack enough to hold references to all the objects in the heap?

The usable stack size is a little less than a megabyte (this is determined by the operating system); on a 64-bit platform that's enough for around 100K pointers, which (very roughly) translates to several tens of thousand local variables distributed across all functions that currently have an activation (there's no specific number because "it depends"). So it's certainly possible to have more objects on the heap than can be referred to directly from the stack, but this is not typically a limitation that code runs into: for example, you could have a local variable pointing at an array, and that array can in turn refer to thousands of other objects. That way, a single stack slot can keep many objects easily accessible.

(Nitpick: millions of objects are never created "simultaneously", they're always allocated after each other, maybe in quick succession.)

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks a lot for the clear explanation.
Would you be able to correct my assumption please? JS doesn't use C/C++ style stack and heap memory allocations, rather it uses a Call Stack and Heap. Call Stack basically tracks the execution context of the program via pointers and all allocations are done in the heap. Buffers and objects in the heap stores pointers too. Pointers are used by GC to figure out whether an object is reachable or unreachable.
@PrasadBeligala : JS and C generally have the same concepts of "stack" and "heap", differences are (1) in C you can control whether objects are stack- or heap-allocated, whereas in JS the engine decides that for you (and you may assume that everything's conceptually heap-allocated, though that's not necessarily always true in practice); (2) in C you must manually delete objects on the heap, whereas in JS the garbage collector frees them for you.
Thanks, so, it looks like, depending on the stack size, JS may use heap to allocate even primitive variables. When I tested two primitive variables (1) a large number (23 bytes), (2)a large string (10MB) and an empty code, I could clearly see the increase of heap for first two cases. But in all three cases, just 58 bytes of Stack size was enough to execute the code without a stack overflow.
@PrasadBeligala: See the first sentence of en.wikipedia.org/wiki/Call_stack: In computer science, a call stack is a stack data structure that stores information about the active subroutines of a computer program. This kind of stack is also known as an execution stack, program stack, control stack, run-time stack, or machine stack, and is often shortened to just "the stack".
|
1

the key point here is that the code includes a garbage collector, once a memory allocation has been narked as not required, the GC is allowed to reallocated for a different use

this is a little oversimplified but lets looks at what's happening at each stage

let x = 3;
let y = x; 
x = 2; 

**sudo**
allocate x;
set x = 3;
allocate y;
set y = read x
set x = 2

so the stack never exceeds a single state that is scoped to 2 allocuted variables

for your loop example

for(let i = 0; i<99999999; i++){ //
   let x = i
}

**sudo**
label forloop;
allocate i;
set i = 0;

allocate x;
set x = read i;

if i < 99999999 
    set i = read i + 1
    goto forloop
else
    deallocate x;
    deallocate i;

here their may be some growth in the memory usage from the allocation of the x variable in the loop however GC can easily clean this up

now for the issue you haven't considered functions and recursion

function doSomething(i){
    if(i>99999999){
        return i;
    else
        return doSomething(i+1);
}
doSomething(0)

**sudo**
goto main;
label doSomething;

if read current_state_i  < 99999999
    allocate new_state;
    allocate i in new_state;
    set new_state_i = read current_state_i + 1
    stack_push doSomething using new_state
    //stack_pop returns here
    set current_state_return = read new_state_return
else
    set current_state_return = current_state_i
    
deallocate new_state
goto stack_pop

label main;
allocate dosomething_state;
allocate i in dosomething_state;
set dosomething_state_i = 0;
stack_push doSomething using dosomething_state
//stack_pop returns here

for this one every time you call the function doSomething the stack has an state added to it this state is a memory slot that stores the passed in value and any variables created in scope and the return

when doSomething is completed this state is marked as no required however until the code can return the system has to keep allocating more and more room on the stack for each calls state this is why recursion is much less safe than normal loops

Comments

-1

As commenters have already said, there is no stack involved in your example, or at least not how you imagine there is.

If you want to break your stack, try this instead:

function Overflow(count){
    console.log('count:', count)
    return Overflow(count + 1)
}

Overflow(0)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.