js 内存
# 内存
# 内存生命周期
不管什么程序语言,内存生命周期基本是一致的:
分配内存
使用内存(读或写)
释放内存
# 内存管理系统:手动 or 自动
低级语言:像 C 语言这样的低级语言一般都有底层的内存管理接口,比如 malloc()和 free()。
高级语言:JavaScript 是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放。 释放的过程称为垃圾回收。这个“自动”是混乱的根源,并让 JavaScript(和其他高级语言)开发者错误的感觉他们可以不关心内存管理。
内存:由可读写单元组成,表示一片可操作空间;
管理:人为的去操作一片空间的申请、使用和释放;
内存管理:开发者主动申请空间、使用空间、释放空间;
管理流程:申请-使用-释放
# 内存引用
如果一个对象可以访问另一个对象(可以是隐式的或显式的),则称该对象引用另一个对象。例如, 一个 JavaScript 引用了它的 prototype (隐式引用)和它的属性值(显式引用)。
# 火焰图
火焰图的生成过程有两步:
使用 Profiler 工具生成 trace 文件;
将 trace 文件转换为 svg 格式的火焰图文件;
FlameGraph(制图工具):包含堆栈跟踪的任何概要文件数据生成火焰图,包括从以下概要分析工具
Linux: perf, eBPF, SystemTap, and ktap
Solaris, illumos, FreeBSD: DTrace
Mac OS X: DTrace and Instruments
Windows: Xperf.exe
参考:https://zhuanlan.zhihu.com/p/147875569 (opens new window)
http://liuxiang.github.io/2018/02/05/火焰图(flame graph)是性能分析/ (opens new window)
# 内存泄漏
内存泄漏 memory leak:是指程序在申请内存后,无法释放已申请的内存空间
一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
memory leak 会最终会导致 out of memory!
合理的使用 WeakMap 和 WeakSet,能帮助我们避免内存泄漏
常见的内存泄漏:
意外的全局变量
dom 清空时,还存在引用
定时器中的内存泄漏
不规范地使用闭包
# 1. 意外的全局变量
变量并没有声明.JS 就会默认将它变为全局变量,这样在页面关闭之前都不会被释放.
function foo() {
bar = 2;
console.log("bar没有被声明!");
}
2
3
4
避免:使用严格模式
# 2.dom 清空时,还存在引用
很多时候,为了方便存取,经常会将 DOM 结点暂时存储到数据结构中.但是在不需要该 DOM 节点时,忘记解除对它的引用,则会造成内存泄露.
var refA = document.getElementById("refA");
document.body.removeChild(refA); // dom删除了
console.log(refA, "refA"); // 但是还存在引用
// 去掉DOM节点的引用
refA = null;
2
3
4
5
与此类似情景还有: DOM 节点绑定了事件, 但是在移除的时候没有解除事件绑定,那么仅仅移除 DOM 节点也是没用的
避免:dom 引用置为 null
# 3. 被遗忘的定时器和回调函数
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
node.innerHTML = JSON.stringify(someResource));
}
}, 1000);
2
3
4
5
6
7
如果没有清除定时器,那么 someResource 就不会被释放,如果刚好它又占用了较大内存,就会引发性能问题. 但是 setTimeout ,它计时结束后它的回调里面引用的对象占用的内存是可以被回收的. 当然有些场景 setTimeout 的计时可能很长, 这样的情况下也是需要纳入考虑的.
避免: 在定时器完成工作的时候,手动清除定时器
# 4. 不规范地使用闭包
相互循环引用
function foo() {
var a = {};
function bar() {
console.log(a);
}
a.fn = bar;
return bar;
}
2
3
4
5
6
7
8
bar 作为一个闭包,即使它内部什么都没有,foo 中的所有变量都还是隐式地被 bar 所引用。 即使 bar 内什么都没有还是造成了循环引用,那真正的解决办法就是,不要将 a.fn = bar.
注意点:
闭包,闭包和内存泄漏没有半毛钱关系,只是由于 IE9 之前的版本垃圾收集机制的原因,导致内存无法进行回收,这是 IE 的问题,现代浏览器基本都不存在这个问题。当然闭包要是使用不当肯定是会造成内存泄漏。
# vue 中的内存泄漏
- DOM 中的 addEventLisner 函数及派生的事件监听没有解除绑定
解决办法:需要在 beforeDestroy 中做对应解绑处理;
组件中使用了 setInterval,需要在 beforeDestroy 中做对应销毁处理
绑在 EventBus 的事件没有解绑
解决办法:this.$EventBus.$off()
- Echarts 的实例没有释放
解决办法:echarts 的实例在 beforeDestroy()方法释放
beforeDestroy () {
this.chart.clear()
}
2
3
# 怎么避免内存泄漏
减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收(即赋值为 null);
避免循环引用,避免“死循环”之类的 ;
避免创建过多的对象 原则:不用了的东西要记得及时归还。
减少层级过多的引用
# 内存泄漏的排查方法
- chrome Performance
启用 Memory(内存)复选框。开始录制前先点击垃圾回收-->点击开始录制-->点击垃圾回收-->点击结束录制
使用 Chrome 的开发者工具 Memory 来进行快照对比。
如果是在 Node 环境下,可以用 Node 提供的 process.memoryUsage()方法来检查内存泄露
参考:https://blog.csdn.net/c11073138/article/details/84700482 (opens new window)
# 内存溢出
内存溢出 out of memory:是一种程序运行出现的错误, 是指程序在申请内存时,没有足够的内存空间供其使用
# 内存溢出的原因
内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
集合类中有对对象的引用,使用完后未清空,使得 JVM 不能回收;
代码中存在死循环或循环产生过多重复的对象实体;
使用的第三方软件中的 BUG;
启动参数内存值设定的过小
# 内存溢出的解决方案
修改 JVM 启动参数,直接增加内存。(-Xms,-Xmx 参数一定不要忘记加。)
检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。
检查代码中是否有死循环或递归调用
检查是否有大循环重复产生新对象实体。
检查对数据库查询中,是否有一次获得全部数据的查询(如果一次取十万条记录到内存,就可能引起内存溢出)
# 栈内存和堆内存
参考:「前端进阶」JS 中的栈内存堆内存https://juejin.cn/post/6844903873992196110 (opens new window)
指针就是通过一个变量去指向一个有的变量或者说是对象,从而产生一种绑定
# 栈内存
# 堆内存
# 栈内存和堆内存对比
栈内存和堆内存的优缺点:
在 JS 中,基本数据类型变量大小固定,并且操作简单容易,所以把它们放入栈中存储。 引用类型变量大小不固定,所以把它们分配给堆中,让他们申请空间的时候自己确定大小,这样把它们分开存储能够使得程序运行起来占用的内存最小。
栈内存由于它的特点,所以它的系统效率较高。
堆内存需要分配空间和地址,还要把地址存到栈中,所以效率低于栈。
# 闭包与堆内存
闭包中的变量并不保存中栈内存中,而是保存在堆内存中。 这也就解释了函数调用之后之后为什么闭包还能引用到函数内的变量。
# 栈内存和堆内存的垃圾回收
栈内存中变量一般在它的当前执行环境结束就会被销毁被垃圾回收制回收, 而堆内存中的变量则不会,因为不确定其他的地方是不是还有一些对它的引用。 堆内存中的变量只有在所有对它的引用都结束的时候才会被回收。