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);