了解内存密集型任务中的 JavaScript 垃圾收集行为

问题描述 投票:0回答:1

我目前正在研究与我正在开发的应用程序中的 JavaScript 垃圾收集相关的内存使用模式。我构建了一个简单的基准测试来更好地理解 GC 的行为。它包括两个单独的任务(任务 A 和任务 B),如下所示。

  const button = document.getElementById('begin-test');
  button.onclick = () => {
    console.log('start');

    const LOOPS = 40000000;
    let array = [];

    // Task A
    for (let index = 0; index < LOOPS; index++) {
      const value = [{ value: index }];
      array.push(value[0].value);
    }

    // Task B
     for (let index = 0; index < LOOPS; index++) {
       array.push(index);
    }

    array = null;

    console.log('done');
  }
}

任务 A 和任务 B 是分开执行的,因为我在测试期间交替评论其中之一。不再执行 Javascript。

应用程序启动时内存使用量约为 3MB。执行任务 A 后,Chrome 的内存检查工具显示内存使用量为 230MB。强制垃圾回收会将其恢复到 3MB。然而,执行任务 B 后,内存使用量报告为 580MB(!)。再次强制进行垃圾回收可将内存使用量减少至 3MB 左右。此行为在每个测试的新选项卡中都是一致的。

我对这种行为有三个疑问:

  1. 任务 A 中的显式内存分配是否导致其较低的“内存浪费”?
  2. 为什么设置
    array = null
    不会立即触发垃圾回收? (
    array = []
    array.length = 0
    都会产生相同的结果)
  3. 在这种特定情况下,我的测试的简单性是否会影响其准确性?
javascript performance garbage-collection heap-memory benchmarking
1个回答
0
投票

GC 不会在取消引用资源后立即释放资源。不要依赖这样的假设

  1. 当你分配内存时,JS堆内存应该增加(通常会减少,因为分配内存后GC通常会释放之前分配的取消引用资源)
  2. 当您取消引用资源时,GC 会释放其内存(不,GC 在认为更合适时会释放内存)。

class HttpClient {
  request(callId) {
    return new Promise(async(resolve, reject) => {
      try{
        const response = await new Promise((resolve, reject) => {
          if(Math.random() > .33) {
            setTimeout(() => resolve(Math.random() > .5 ? {error:'session expired'} : {status: 'ok'}), 1000);
          }else{
            setTimeout(() => reject(new Error('some api error has happenned')), 1000);
          }
        });
        if(response.error === 'session expired'){
          console.log(callId + ': session expired, redirecting to login route'); 
          return;
        }
        resolve(response);
      }catch(e){
        reject(e);
      }
    });
  }
}

let prevMemory;

const http = new HttpClient;
const registry = new FinalizationRegistry(obj => {
  console.log(obj, 'released');
});
let callId = 0;
async function test(){
  const id = ++callId;
  
  console.log('start ' + id);
  prevMemory = performance.memory.usedJSHeapSize;
  
  await new Promise(r => requestAnimationFrame(r));

  const LOOPS = 40000000;
  let array = [];
  registry.register(array, `array ${id}`);

  // Task A
  for (let index = 0; index < LOOPS; index++) {
    const value = [{ value: index }];
    array.push(value[0].value);
  }

  // Task B
   for (let index = 0; index < LOOPS; index++) {
     array.push(index);
  }

  array = null;

  console.log('done ' + id);
  console.log('allocated bytes:', performance.memory.usedJSHeapSize - prevMemory);
  prevMemory = performance.memory.usedJSHeapSize;
  
}
button{
  padding: 5px 30px;
  border-radius:5px;
  box-shadow: 0 0 10px rgba(0,0,0,.2);
  cursor:pointer;
}
<button onclick="test()">test</button>

© www.soinside.com 2019 - 2024. All rights reserved.