Why does the heap size limit readjust to larger size in nodejs during a memory leak instead of crashing?

Viewed 16

I was trying to recreate a FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory that I get in production on heroku.

But my local machine will readjust the heap limit.

If I set the heap size limit node --max-old-space-size=200 ./bin/www.js the limit seems to readjust as I near it. It goes from around 200mb to over 800mb. I would expect it to stay at 200mb and crash. Why does this happen?

Reproduced memory leak

const crashArray = [];
...
 app.get('/leak', async (req, res, next) => {
    v8Print('before push to array');
    crashArray.push(...Array(0.12e6).fill('some string'));
    crashArray.push(...Array(0.12e6).fill('some string'));
    crashArray.push(...Array(0.12e6).fill('some string'));
    crashArray.push(...Array(0.12e6).fill('some string'));
    crashArray.push(...Array(0.12e6).fill('some string'));
    crashArray.push(...Array(0.12e6).fill('some string'));

    v8Print('after push to array');

    res.sendStatus(200);
  });

Log function

const v8 = require('v8');

// for memory allocation heap debugging
module.exports.v8Print = (message) => {
  const heapStats = v8.getHeapStatistics();
  const heapStatsMB = heapStats;
  for (const key in heapStatsMB) {
    heapStatsMB[key] = `${(((heapStatsMB[key] / 1024 / 1024) * 100) / 100).toFixed(2)} MB`;
  }
  console.log('');
  console.log(message);
  console.table(heapStatsMB);
  console.log('');
};

I call the GET /leak request a bunch of times to reproduce a leak. When I get close the heap limit adjusts.

before push to array
┌─────────────────────────────┬─────────────┐
│           (index)           │   Values    │
├─────────────────────────────┼─────────────┤
│       total_heap_size       │ '155.28 MB' │
│ total_heap_size_executable  │  '0.80 MB'  │
│     total_physical_size     │ '154.35 MB' │
│    total_available_size     │ '122.37 MB' │
│       used_heap_size        │ '114.22 MB' │
│       heap_size_limit       │ '248.00 MB' │
│       malloced_memory       │  '0.01 MB'  │
│    peak_malloced_memory     │  '2.10 MB'  │
│      does_zap_garbage       │  '0.00 MB'  │
│  number_of_native_contexts  │  '0.00 MB'  │
│ number_of_detached_contexts │  '0.00 MB'  │
└─────────────────────────────┴─────────────┘


after push to array
┌─────────────────────────────┬─────────────┐
│           (index)           │   Values    │
├─────────────────────────────┼─────────────┤
│       total_heap_size       │ '169.45 MB' │
│ total_heap_size_executable  │  '0.80 MB'  │
│     total_physical_size     │ '168.53 MB' │
│    total_available_size     │ '175.61 MB' │
│       used_heap_size        │ '128.00 MB' │
│       heap_size_limit       │ '848.00 MB' │ // <----- heap size limit changes
│       malloced_memory       │  '0.01 MB'  │
│    peak_malloced_memory     │  '2.10 MB'  │
│      does_zap_garbage       │  '0.00 MB'  │
│  number_of_native_contexts  │  '0.00 MB'  │
│ number_of_detached_contexts │  '0.00 MB'  │
└─────────────────────────────┴─────────────┘
0 Answers
Related