- V8引擎如何回收垃圾
- 如何查看v8内存使用情况
- 内存优化实例
V8引擎如何回收垃圾
思考:打开页面,页面卡顿,卡死,提示页面无响应 ———— 内存过大
为什么我们要关注内存? (如何处理变量)
- 防止页面占用内存过大, 引起客户端卡顿,甚至无响应
- Node使用的也是V8, 内存对于后端服务的性能至关重要,因为服务的持久性,后端很容易造成内存泄漏
V8的垃圾回收机制-内存分配
在V8中,主要将内存分为新生代和老生代两代。
- 新生代中的对象为存活时间较短的对象
- 老生代中的对象,为存活时间较长或常驻内存的对象.
1
2
3
4
5
6
---------------------------------------------
| | |
| 新生代的 | 老生代的内存空间 |
| 内存空间 | |
| | |
---------------------------------------------
内存大小
- 和操作系统有关64位为1.4G,32位为0.7G
- 64位下新生代的空间是64MB,老生代为1400MB
- 32位下新生代的空间是16MB,老生代为700MB
为什么不把内存设置大些呢?
- 1 前端的特点是不持久化,执行一遍就全部会回收了(js设计之处是为了浏览器),1.4G完全足够用了。
- 2 js回收内存的时候,会暂停执行(回收一次100mb,大概需要6ms, 太大回收会卡顿)
垃圾回收算法:
- 新生代回收是复制(from、to)—— 新生代放一些临时变量
- 老生代标记删除回收
总结:
新生代存放一些新产生的变量,存在比较小,存在时间比较短的变量。 分成了From空间和To空间,刚开始新生代的变量,放到from空间里,当我们满足一定条件,新生代要进行回收了,活着的(还有用的)变量复制到To空间,然后清空From空间,下次再发生回收, From空间和To空间的角色发生互。新生代为什么要用怎么设置,牺牲空间换取时间,节省了时间,有一半空间用不上。
老生代是新生代变量已符合老生代,才放到老生代。标记删除回收
Scavenge 算法(新生代的垃圾回收)
在分代的基础上,新生代中的对象主要通过Scavenge算法进行垃圾回收.在Scavenge算法的具体实现中,主要采用Cheney算法.Cheney算法是一种采用复制方式实现的垃圾回收
.它将堆内存一分为二,每一部分空间称为semispace. 只有一个处于使用中,另一个处于闲置状态
- From空间: 处于使用状态的semispace空间, 当开始进行垃圾回收时, 会检查From空间中的存活对象,
- To空间: 处于闲置状态的空间, 将From空间存活对象被
复制
到To空间, 而非存活空间对象占用的空间将被释放 - 完成复制后, From空间和To空间的角色发生互换
简而言之, 在垃圾回收的过程中,就是通过将存活对象在两个semispace空间之间进行复制
. Scavenge中之复制活着的对象
Scavenge算法是典型的牺牲空间换取时间
的算法, 非常适合在新生代中, 因为新生代中对象的生命周期较短。
1
2
3
4
5
6
7
---------------------------------------------------------------
| | | |
| semi | semi | |
| space | space | 老生代空间 |
| (From) | (To) | |
| | | |
---------------------------------------------------------------
对象晋升(晋升到老生代中)的条件主要有两个:
- 对象是否经历Scavenge回收(对象从From空间中复制到To空间时,检测内存地址判断是否经历Scavenge回收。是则复制到老生代空间中)
- To空间的内存占用比超过限制(没有经历Scavenge回收, To空间已经使用超过了25%(8M), 则这个对象直接晋升到老生代中)
总结流程:
- semi space(From) => 是否经历Scavenge回收 => 是则晋升老生代(第二次回收,还存活),否则到semi space(To)
- semi space(From) => To空间是否已经使用了25% => 是则晋升老生代,否则到semi space(To)
设置25%这个限制值的原因是当次Scavenge回收完成后,这个To空间将变成From空间, 接下来的内存分配将在这个空间进行.如果占比过高会影响后来的内存分配.(为什么不实时回收,回收内存会暂停执行,回收一次100mb,需要6ms)
思考: 对象晋升后,将会在老生代空间作为存活周期较长的对象对待,接受新的回收算法处理.
Mark-Sweep & Mark-Compact(老生代垃圾回收)
Mark-Sweep标记清除,它分为标记和清除两个阶段。Mark-Sweep在标记阶段遍历堆中所有对象.并标记活着的对象
,在随后的清除阶段中,之清除没有被标记的对象
.
缺陷:内存空间会出现不连续的状态(内存碎片会对后续的内存分配造成问题)
Mark-Compact标记整理, 是在Mark-Sweep基础上演变而来.它们的差别在于对象标记死亡后,在整理的过程中,将活着的对象往一端移动,移动完成后,直接清理掉边界外的内存
为什么要整理?
内存碎片会对后续的内存分配造成问题。比如数组必须是连续内存空间
可以看出,Scavenge中之复制活着的对象,而Mark-Sweep只清理死亡对象. 活对象在新生代中只占较小部分,死对象在老生代中只占较小部分,这是两种回收方式能高效处理的原因.
-
如何查看v8内存使用情况
内存查看
- 浏览器:window.performance;
- node-process.memoryUsage();
1
2
3
4
5
6
7
8
9
10
11
// 获取内存的大小的方法
function getme(){
var mem = process.memoryUsage();
var format = function(bytes){
return (bytes/1024/1024).toFixed(2)+'MB';
}
// 总的内存,和使用内存
console.log("heapTotal:" + format(mem.heapTotal) +
'heapUsed:' + format(mem.heapUsed));
}
getme();
扩展知识:node是c++编写的, 可以扩展c++内存。
变量:全局变量和局部变量
- 内存主要就是存储变量等数据的
- 局部变量当程序执行结束,且没有引用的时候就会随着消失
- 全局对象会始终存活到程序运行结束
扩展:闭包会不会消失,看使用,只是进行闭包是会消失,如果被引用则不会消失。闭包不是具体的写法,是一种思想。让我的变量,通过指定的方式,外部可以访问。这就是闭包。
1
2
3
4
5
6
function a(){
var b = 123;
}
a(); // a执行完。b在外部没有引用,b局部变量伴随a的作用域一起回收
var a = 123; // 整个程序完了,才会结束回收
用node运行以下代码,内存不够进行变量回收(发现都是全局变量,无法回收)=> 内存不够,导致内存溢出,中断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var size = 20 * 1024 * 1024;
var arr = new Array(size);
var arr2 = new Array(size);
var arr3 = new Array(size);
var arr4 = new Array(size);
var arr5 = new Array(size);
var arr6 = new Array(size);
var arr7 = new Array(size);
var arr8 = new Array(size);
var arr9 = new Array(size);
var arr10 = new Array(size);
var arr11 = new Array(size);
var arr12 = new Array(size);
// 到这里内存不够了
var arr13 = new Array(size);
console.log(1);
局部会回收,只是说可以回收,并不是用完就回收。 运行的时候卡了一下,因为这里内存接近满的时候,进行回收会暂停执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var size = 20 * 1024 * 1024;
var a = []
function b(){
var arr1 = new Array(size);
var arr2 = new Array(size);
var arr3 = new Array(size);
var arr4 = new Array(size);
var arr5 = new Array(size);
}
b(); // 标记b方法里变量可以被回收了
for(var i = 0; i < 13; i++){
a.push(new Array(size));
getme();
}
如何注意内存的使用
一、优化内存的技巧
- 尽量不要定义全局变量
- 全局变量记得及时销毁掉
- 用匿名自执行函数把全局变为局部
- 尽量避免闭包(是错的,应该是尽量避免闭包引用)
全局变量记得及时销毁掉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 不推荐在开发时写delete && delete在严格模式下有bug
var size = 20 * 1024 * 1024;
var arr1 = new Array(size);
arr1 = undefined; // 及时回收 undefined | null
var arr2 = new Array(size);
arr2 = undefined;
var arr3 = new Array(size);
arr3 = null;
var arr4 = new Array(size);
arr4 = null;
var arr5 = new Array(size);
arr5 = null;
var arr6 = new Array(size);
var arr7 = new Array(size);
var arr8 = new Array(size);
var arr9 = new Array(size);
var arr10 = new Array(size);
var arr11 = new Array(size);
var arr12 = new Array(size);
// 到这里内存不够了
var arr13 = new Array(size);
var arr14 = new Array(size);
var arr15 = new Array(size);
console.log(1);
扩展:null表示没有对象,即该处不应该有值。 undefined表示缺少值,即此处应该有值,但没有定义。null是一个保留字, undefined其实是一个变量。 比如null = 123; 会报错。但undefined不会报错
用匿名子执行函数把全局变为局部
1
2
3
4
// 比如jquery等
(function(){
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function a(){
var size = 20 * 1024 * 1024;
var arr1 = new Array(size);
// 局部变量,return。被外部引用,又不释放。导致内存溢出
return arr1;
}
// 闭包,被外部引用
var b1 = a();
var b2 = a();
var b3 = a();
var b4 = a();
var b5 = a();
var b6 = a();
var b7 = a();
var b8 = a();
var b9 = a();
var b10 = a();
var b11 = a();
var b12 = a();
// 内存溢出
var b13 = a();
防止内存泄漏
- 不要滥用缓存,尽量不要用v8来缓存
- 大内存量操作
缓存,全局变量a超出内存大小限制,内存泄漏。尽量不要用v8来缓存。
解决方案:
- 方案1. node用redis缓存;
- 方案2. 给缓存加个锁限制
1
2
3
4
5
6
7
8
9
10
var size = 20 * 1024 * 1024;
// 缓存通常都是在全局
var a = []; // 缓存
for(var i = 0; i < 16; i++){
// 给缓存加个限制
if(a.length > 4){
a.shift();
}
a.push(new Array(size));
}
尤其是node服务,不断访问,访问数量很多,会很容易造成内存泄漏。如果用node服务,全局变量,只要服务开着,全局变量不会被回收
大内存量操作 —— node
比如:主要在node操作文件, fs.readFile这个api是一次性读取文件到buffer
1
2
fs.readFile(); // 大文件不可取
createReadStream().pipe(write); // 大文件
大内存量操作 —— 浏览器
比如:大文件上传,可以用切片上传。file blog slice
1
2
3
// 就像字符串分割
file.slice(0, 1000);
file.slice(1000, 2000);
收尾:为什么我们要了解底层。如果一个东西谁都会,那个这个东西就会变得不值钱;了解底层才能不是一个简单搬砖工;除了底层外,视野,技术的全面性也是格外重要。
底层类, 一些稀奇古怪的js题,或者直接某个底层技巧(JavaScript异步机制、JavaScript回收机制,JavaScript变量机制)
源码类, Vue(比如axios、vuex、vue-route源码)、React
项目,项目里面做了什么事情,或者介绍一些项目。(一些操作,同构、移动解决方案、组件库、架构、
一些优化操作(比如管理keep-alive, 如何分模块管理页面、缓存操作))