JavaScript内存管理
通过了解内存管理进行代码优化,提高代码运行性能、避免产生不必要的Bug。
内存管理介绍
- 内存:由可读写单元组成,表示一片可操作空间
- 管理:人为的(开发者)去操作一片空间的申请,使用和释放
- 内存管理:开发者主动申请空间,使用空间,释放空间
JavaScript中的内存管理
ECMAscript中并没有提供内存管理相应的api供开发者主动调用。
但是它并不会脱离内存申请-使用-释放的基本管理流程。
// 申请
let obj = {}
// 使用
obj.name = 'laor'
// 间接方式释放
obj = null
JavaScript垃圾回收
- JavaScript中的内存管理是自动的,创建对象时会自动分配空间
- 当对象不被引用时就认为它是垃圾
- 对象不能从根上访问到时就是垃圾
找到垃圾,执行引擎对其进行清理,进行空间的释放和回收。
JavaScript中的可达对象
- 可以访问到的对象就是可达对象(引用、作用域链)
- 可达的标准就是从根出发是否能够被找到
- JavaScript中的根可以理解为全局变量对象
JavaScript中的引用与可达
看一个栗子:
let obj = { name:'laor' }
// laor对象空间多了一个引用(引用计数)
let ali = obj
obj = null
首先从根上可以找到obj ,从而可以找到laor的对象空间,它是可达的
然后将obj赋值给ali, laor对象空间多了一个引用(引用计数)
将obj赋值为null,此时laor对象空间仍然是可达的 ,因为ali还对其存在引用
稍微复杂一点的:
// 循环引用的例子
function objGroup(obj1,obj2){
obj1.next = obj2
obj2.pre = obj1
return {
o1:obj1,
o2:obj2
}
}
let obj = objGroup({name:'obj1'},{name:'obj2'})
console.log(obj)
delete obj.o1
delete obj.o2.pre
可达对象图示
GC 算法介绍
GC定义与作用
- GC就是垃圾回收机制的简写
- GC可以找到内存中的垃圾、并释放和回收空间
GC中的垃圾是什么?
- 程序中不再需要使用的对象
function fn() {
name = 'lg' // 只从程序角度出发name是不需要了,不关心是否真的会被回收
console.log(`${name} is a coder`)
}
fn()
- 程序中不能再访问到的对象
function fn() {
const name = 'lg'
console.log(`${name} is a coder`)
}
fn()
GC算法是什么?
- 是一种机制,垃圾回收器完成具体的工作
- 工作的内容就是查找垃圾释放空间、回收空间
- 算法就是工作时查找和回收所遵循的规则
常见的GC算法
- 引用计数
- 标记清除
- 标记整理
- 分代回收
引用计数
- 核心思想:通过引用计数器设置引用数,判断当前引用数是否为0
- 引用关系改变时修改引用数字
- 引用数字为0时立即回收
优点
- 发现垃圾时立即回收
- 减少程序卡顿时间:当程序内存不足时立即将引用数为0的垃圾回收
缺点
- 无法回收循环引用的对象
- 资源消耗较大 ,因为要维护引用数值的变化,需要时刻监控对象的引用数值是否修改
标记清除
- 核心思想:分标记和清除二个阶段完成
- 遍历所有对象找标记活动对象(可达)
- 遍历所有对象清除没有标记对象(不可达)
- 回收相应的空间
优点
- 相对于引用计数,能够解决循环引用对象不能回收问题
缺点
- 垃圾回收后会导致空间碎片化,空间地址不连续,浪费空间
- 不会立即回收垃圾对象
标记整理
- 标记整理可以看做是标记清除的增强
- 标记阶段的操作和标记清除一致
- 清除阶段会先执行整理,移动活动对象位置,然后清除,减少碎片化空间
认识V8
- V8是一款主流的JavaScript执行引擎
- V8采用即时编译
- V8内存设限:64位不超过1.5G,32位不超过800M
V8垃圾回收策略
-
采用分代回收的思想
-
内存分为新生代((32M/16M))和老生代(1.4G/700M)存储空间,小空间存储新生代对象(新生代指的是存活时间较短的对象)
-
针对不同对象采取不同GC算法
V8常用GC算法
- 分代回收
- 空间复制
- 标记清除
- 标记整理
- 标记增量
V8 回收新生代对象:空间换时间
- 将新生代存储空间也一分为二,使用空间为From,空闲为To
- 回收过程采用复制算法 + 标记整理:活动对象存储在From空间,标记整理后将活动对象拷贝至To
- 将From空间完全释放From与To交换空间身份
注:经过一轮GC之后还活着的对象 以及 拷贝过程中To的使用率超过了25% ,会移动至老生代对象空间
V8 回收老生代对象
-
主要采用标记清除,标记整理,增量标记算法
-
首先使用标记清除完成垃圾空间的回收
-
当把新生代的对象移往老生代的时候,当空间不足时,就会进行标记整理进行空间优化
-
采用增量标记进行效率提升:当进行垃圾回收时会阻塞代码执行,所以将回收过程拆分成多段提高效率
性能优化
内存问题
简单归类为
- 内存泄漏:内存使用程序升高,无下降
- 内存膨胀:应用程序本身设计需要较高的内存使用,在多数设备上都存在性能问题
- 频繁垃圾回收:可以通过Performance面板内存变化图,浏览器任务管理器中JavaScript使用的内存变化分析
优化点
- 慎用全局变量
- 缓存全局变量 如
document - 通过原型新增方法,而不需放在构造函数内部
- 合理使用闭包,谨防内存泄漏,DOM引用及时清除
- For循环的优化,length进行提前保存,执行性能forEach > for > for in
- 文档碎片优化DOM节点添加
- 克隆优化节点操作