Skip to content

02-v8引擎回收内存

统计信息:字数 5296 阅读11分钟

为什么学习内存:

  • 前端:防止前端界面内存占用过大,造成界面卡顿甚至无响应

  • 后端:Node 使用的也是V8引擎,内存对于后端服务的性能很重要。后端服务的持久性和健壮性,后端很容易造成内存溢出。

为什么前端要了解底层?现在前端的技术全面性很重要。不一定要使用express完成服务器端框架设置,但是需要了解原理(增加竞争力)。

1、V8 引擎 GC 机制

V8的内存分为新生代内存空间和老生代内存空间(新生代很小)。V8引擎内存和操作系统相关,64位操作系统1.4G(64MB+1400MB),32位系统0.7G(16MB+700MB)。

为什么设计成1.4G?

  • JS最初设计为了前端,需要的数据不是持久性的,执行一次就GC,1.4足够
  • JS 执行 GC 时,会暂停脚本执行,如果太呆,GC消耗时间

新生代和老生代数据区别?

  • 新生代存放短期的最新的数据,老生代存放较老的数据。
  • 数据转换:如果新生代的某个数据(数组)已经经过一次GC,同时新生代To空间已经使用了25%,那么这个新生代数据就放在老生代中。

GC 算法区别:

新生代内存处理算法:复制,新生代内存分成两部分(from-to)首先在一个部分存放变量。当V8觉得需要清空内存时,将存放变量部分中有用的变量复制到另一个区域中,然后清空这个区域。下一次清空内存时,操作对调。牺牲了空间复杂度(只使用一半内存),优化了时间复杂度(直接复制)。

老生代内存处理算法:标记、删除、整理三步:标记有用的内存和无用的内存,删除无用的内存,把离散的有用的内存整理成连续的有用的内存。为什么要整理内存,把离散的内存占用整理成连续的内存占用?因为一部分变量需要连续的内存(例如数组,如果都是分散的内存地址,那么无法创建新的变量)

在node环境下可以直接使用JS代码判断内存。Node 用C++写的,所以可以手动扩展内存。在浏览器中不能扩展内存。

以前的知识体系中,闭包会影响内存的清理。现在的技术可以解决这个问题。所以看书的时候一定注意书籍的时间效应(视频课程也一样)。

2、查看V8内存使用情况

node环境

查看内存使用情况 process.memoryUsage() 直接执行

console.log(process.memoryUsage());

获取下面的信息

{ rss: 21417984, heapTotal: 7159808, heapUsed: 4447496, external: 8224 }

为了便于阅读,我们使用下面的函数转换成 MB

var format = function(bytes) {
  return (bytes / 1024 / 1024).toFixed(2) + 'MB';
} 
function getMemory() {
  var mem = process.memoryUsage();
  var str1 = 'Process: heapTotal ' + format(mem.heapTotal);
  var str2 = 'heapUsed ' + format(mem.heapUsed);
  var str3 = 'rss ' + format(mem.rss);
  return str1 + str2 + str3;
}

浏览器环境

在浏览器环境中 window.performance

window.performance.memory

totalJSHeapSize/usedJSHeapSize/jsHeapSizeLimit 判断使用的比例

V8如何处理变量和内存呢?

内存主要用于存储数据(变量)。变量分为全局变量和局部变量:全局变量始终存在,直到程序运行结束;局部变量会当程序执行结束,并没有外部引用的时候,就会删除变量,释放内存。

// 避免使用很多全局变量
var size = 20 * 1024 * 1024;
var arr = new Array(size);

// 使用函数避免全局变量
function b() {
  var arr = new Array(size);
}
b();

test.js

var http = require('http');
var a = [];

http.createServer(function() {
  function getMe() {
    //
  }
  a.push(new Array(20 * 1024 * 1024));
  getMe();
}).listen(3000);

3、内存优化实例

  • 尽量不定义全局变量
  • 全局变量使用后要销毁
  • 匿名函数自执行把全局变量转换成局部变量
  • 尽量避免闭包后外部引用

销毁变量时,推荐使用 null undefined,不使用 delete(在严格模式下有问题);对于React,在组件卸载阶段,或者不使用这个全局变量时,就移除这个变量,释放内存。

如何放置内存泄漏?

  • 避免滥用缓存:当缓存的内容很多(判断缓存数组的长度),清空早期的缓存数据,加一个锁提示缓存过多(实际项目中的undo-redo存储这操作)
let cache = []; // 缓存通常存放在全局

// 数据操作后放入缓存
// cache.push(item);

// 如果缓存很大,清空一部分缓存
if (cache.length > 10) {
  cache.shift();
}

// 组件卸载时清空缓存
cache = null;
  • 减少大内存操作:前端上传大文件使用分片长传技术;后端文件流技术。前端需要把大文件流,调用 slice 方法后切片,然后循环上传,这样可以避免大量内存(如果上传一个5G的文件,切片上传)
import { createReadStream, write } from 'fs';

fs.readFile();
// readFile 是一次性读取文件到buffer,适合小文件上传
createReadStream().pipe(write);

// 上传大文件使用切片方法: file 是一个 blob 对象,具有 slice 方法
file.slice(0, 1000);
file.slice(1000, 2000);

Last update: November 9, 2024