Search code examples
javascriptnode.jsmemoryheap-memoryeval

Why can't I exhaust the heap by dynamically creating globally-scoped variables in an infinite loop in JavaScript/Node.js?


I want to create global variables over and over until my computer's heap has no more space, and I get an error. I thought the following code would work to do so:

for (let i = 0; true; i++) {
    eval('var a' + i + ' = "I will break you."')
}

But when I run the code, I wait a few minutes, and it just keeps going, apparently not breaking my memory (I figured my program would terminate with an error message, similar to when stack overflow happens). Why doesn't it cause my memory to break, and how could I modify it to accomplish this?

This is in run with Node.js, version 16.15.0.

P.S. - I know that declaring a variable with let in a loop makes the variable scoped to that block, but I figured that var was scoped globally in this case, creating many instances of variables throughout the program.


Solution

  • I think you have a particular test that stresses the v8 allocator/garbage collector and causes it to get stuck before v8 can exhaust the memory heap.

    Modifying your loop slightly, and running node with --trace-gc highlights where progress grinds to a halt.

    for (let i = 1; true; i++) {
      if (i%10000 === 0) console.log(i)
      eval(`var a${i} = "I will break you."`)
    }
    

    On my local node, after 8388608 variables (which is a suspiciously round 8192*1024) the loop doesn't progress much as there is a GC triggered for every allocation (i.e. every iteration of the loop).

    8388601
    8388602
    8388603
    8388604
    8388605
    8388606
    8388607
    8388608
    8388609
    [97743:0x7f9c58078000]    97804 ms: Scavenge 2062.5 (2831.9) -> 1995.0 (2767.9) MB, 5.4 / 0.0 ms  (average mu = 0.896, current mu = 0.827) allocation failure; 
    8388610
    [97743:0x7f9c58078000]   100681 ms: Scavenge 2059.0 (2831.9) -> 1994.9 (2767.9) MB, 2.5 / 0.0 ms  (average mu = 0.896, current mu = 0.827) allocation failure; 
    8388611
    [97743:0x7f9c58078000]   103477 ms: Scavenge 2058.9 (2831.9) -> 1994.9 (2736.9) MB, 1.5 / 0.0 ms  (average mu = 0.896, current mu = 0.827) allocation failure; 
    8388612
    [97743:0x7f9c58078000]   106332 ms: Scavenge 2058.9 (2800.9) -> 1994.9 (2736.9) MB, 1.9 / 0.0 ms  (average mu = 0.896, current mu = 0.827) allocation failure; 
    8388613
    [97743:0x7f9c58078000]   109183 ms: Scavenge 2058.9 (2800.9) -> 1994.9 (2736.9) MB, 1.4 / 0.0 ms  (average mu = 0.896, current mu = 0.827) allocation failure; 
    

    A quicker way to exhaust the heap is to avoid the relatively slow eval in the loop and store references to longer, concatenated strings to avoid v8 being able to refer to the internalised string memory from the code.

    let a = {}
    for (let i = 0; true; i++) {
      a[i] = "I will break you."+i+i+i+i
    }
    
    % node --trace-gc break.js
    [97778:0x7faec0078000]       30 ms: Scavenge 6.0 (6.3) -> 5.8 (7.3) MB, 1.2 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure; 
    [97778:0x7faec0078000]       31 ms: Scavenge 6.0 (7.3) -> 5.9 (8.1) MB, 0.8 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure; 
    [97778:0x7faec0078000]       32 ms: Scavenge 6.7 (8.1) -> 6.8 (10.6) MB, 0.6 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure; 
    [97778:0x7faec0078000]       36 ms: Scavenge 8.0 (10.8) -> 7.8 (11.5) MB, 1.6 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure; 
    [97778:0x7faec0078000]       38 ms: Scavenge 8.7 (11.5) -> 8.8 (16.5) MB, 0.9 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure; 
    <snip>
    [97778:0x7faec0078000]    29994 ms: Scavenge 3990.3 (4071.6) -> 3991.9 (4073.1) MB, 10.7 / 0.0 ms  (average mu = 0.222, current mu = 0.199) allocation failure; 
    [97778:0x7faec0078000]    30019 ms: Scavenge 3992.0 (4073.1) -> 3990.3 (4087.6) MB, 24.2 / 0.0 ms  (average mu = 0.222, current mu = 0.199) allocation failure; 
    [97778:0x7faec0078000]    30037 ms: Scavenge 4006.0 (4087.6) -> 4007.7 (4089.1) MB, 10.0 / 0.0 ms  (average mu = 0.222, current mu = 0.199) allocation failure; 
    [97778:0x7faec0078000]    30058 ms: Scavenge 4007.7 (4089.1) -> 4006.0 (4103.1) MB, 21.6 / 0.0 ms  (average mu = 0.222, current mu = 0.199) allocation failure; 
    
    <--- Last few GCs --->
    
    [97778:0x7faec0078000]    30019 ms: Scavenge 3992.0 (4073.1) -> 3990.3 (4087.6) MB, 24.2 / 0.0 ms  (average mu = 0.222, current mu = 0.199) allocation failure; 
    [97778:0x7faec0078000]    30037 ms: Scavenge 4006.0 (4087.6) -> 4007.7 (4089.1) MB, 10.0 / 0.0 ms  (average mu = 0.222, current mu = 0.199) allocation failure; 
    [97778:0x7faec0078000]    30058 ms: Scavenge 4007.7 (4089.1) -> 4006.0 (4103.1) MB, 21.6 / 0.0 ms  (average mu = 0.222, current mu = 0.199) allocation failure; 
    
    
    <--- JS stacktrace --->
    
    FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
     1: 0x101705fb5 node::Abort() (.cold.1) [/node-18.15.0/bin/node]
     2: 0x100186a29 node::Abort() [/node-18.15.0/bin/node]
     3: 0x100186c0e node::OOMErrorHandler(char const*, bool) [/node-18.15.0/bin/node]
     4: 0x100315cc3 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/node-18.15.0/bin/node]
     5: 0x1004de975 v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/node-18.15.0/bin/node]
     6: 0x1004e2e40 v8::internal::Heap::CollectSharedGarbage(v8::internal::GarbageCollectionReason) [/node-18.15.0/bin/node]
     7: 0x1004df68f v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::internal::GarbageCollectionReason, char const*, v8::GCCallbackFlags) [/node-18.15.0/bin/node]
     8: 0x1004dc768 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/node-18.15.0/bin/node]
     9: 0x1004db762 v8::internal::Heap::HandleGCRequest() [/node-18.15.0/bin/node]
    10: 0x10047c681 v8::internal::StackGuard::HandleInterrupts() [/node-18.15.0/bin/node]
    11: 0x1008e7bf8 v8::internal::Runtime_StackGuard(int, unsigned long*, v8::internal::Isolate*) [/node-18.15.0/bin/node]
    12: 0x100cdddb9 Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit [/node-18.15.0/bin/node]
    13: 0x105a8cc08