JavaScript内存管理
内存:由可读写单元构成,表示一片可操作的空间。
管理:人为的去操作一片空间的申请、使用和释放。
内存管理:开发者主动的去申请空间、使用空间和释放空间。
管理流程:申请-使用-释放。
JavaScript中的内存管理:
申请内存空间,使用内存空间,释放内存空间。
//申请空间
let obj = {}
//释放空间
obj.name = 'aa'
//使用空间
obj = null
JavaScript中的垃圾
JavaScript中内存管理是自动的(每当创建一个函数、数组等变量,会自动的为我们分配一定的空间)
对象不再被引用时是垃圾。
对象不能从根上访问到时是垃圾。
JavaScript中的可达对象:可以访问到的对象就是可达对象(引用、作用域链)。
可达的标准是从根出发是否能找到。
JavaScript中的根可以理解为全局变量对象(也就是全局执行上下文)。
function objGroup (obj1, obj2) {
obj1.next = obj2
obj2.prev = obj1
return {
o1: obj1,
o2: obj2
}
}
let obj = objGroup({name: 'obj1'}, {name: 'obj2'})
console.log(obj)
加入说obj1被delete了那么obj1是从根找不到的,obj1则成了垃圾,JavaScript引擎就会去回收它↓
GC算法
GC就是垃圾回收机制的缩写。
GC可以找到内存中的垃圾,并释放和回收空间。
GC里的垃圾是程序中不需要再使用的对象。
GC算法是什么:
- GC是一种机制,垃圾回收器完成具体的工作
- 工作就是查找垃圾释放空间,回收空间
- 算法就是工作时查找和回收所遵循的规则
常见GC算法:
- 引用计数:通过一个数字,来判断当前的一个对象是不是一个垃圾
- 标记清除:给活动对象添加一个标记,看其是不是一个垃圾
- 标记整理
- 分代回收
引用计数算法实现原理
核心:设置引用数,判断当前引用数是否为0
引用计数器
引用关系改变时,会去修改引用数字
引用数字为0时立即回收
const user1 = {age: 11}
const user2 = {age: 12}
const user3 = {age: 13}
const nameList = [user1.age, user2.age, user3.age]
function fn () {
//没有变量声明,直接被挂载到了window对象
// num1 = 1
// num2 = 2
//内部声明了变量,当fn执行之后,num1,num2的引用计数为0
const num1 = 1
const num2 = 2
}
fn()
优点:
- 发现垃圾时立即回收
- 最大程度减少程序暂停(内存快爆满的情况下立刻寻找引用数值为0的,然后去释放内存)
缺点:
- 无法回收循环引用的对象
- 时间开销大
function fn1() {
const obj1 = {}
const obj2 = {}
//由于有互相指引的关系,所以引用计数不为0
obj1.name = obj2
obj2.name = obj1
return true
}
fn1()
标记清除算法原理
核心:分标记和清除两个阶段完成
遍历所有对象找标记活动对象
遍历所有对象,清除没有标记对象
回收相应空间
会从全局变量开始向下遍历每一个子节点,a1,b1成为了局部变量,会被清除回收。回收的数据会被放到空闲列表上,后续添加的内容会直接从空闲列表中取。相比于引用计数而言,我们可以让循环引用的能够回收。
缺点:地址不连续。比如A对象有2个字节的空间,B对象有一个字节的空间,但是释放空间的时候并不会生成一个3个字节的空间,还是有一个2字节和一个1字节的两个空间,假如正好有个C对象新增进来,那么找2字节的空间就会多出0.5字节,找B又不够。
标记整理算法实现原理
标记整理可以看做是标记清除的增强
标记阶段的操作和标记清除一致
清除阶段会先执行整理,移动对象的位置
V8
v8是一款主流的JavaScript执行引擎
v8采用即时编译
v8内存设限
V8垃圾回收策略
采用分代回收的思想
内存分为新生代和老生代
V8中常用GC算法:
- 分代回收
- 空间复制
- 标记清除
- 标记整理
- 标记增量
v8如何回收新生代对象
V8内存分配:
V8内存空间一分为二
小空间用于存储新生代对象(32M | 16M)
新生代指的是存活时间较短的对象(比如局部变量)
新生代对象回收实现:
- 回收过程采用复制算法+标记整理
- 新生代内存区分为两个等大小的空间
- 使用空间为From,空闲空间为To
- 活动对象储存于From
- 当From空间达到一定值之后,将采用GC算法标记整理后将活动对象拷贝至To
- From与To交换空间完成释放
回收细节说明:
- 拷贝过程中可能出现晋升
- 晋升就是指将新生态对象移动至老生态
- 一轮GC还存活的新生代需要晋升
- To空间的使用率超过25%
V8如何回收老生代对象:
- 老生代对象存放在右侧老生代存放区域
- 64位操作系统1.4G,32位操作系统700M
- 老生代对象就是指存活时间较长的对象
老生代对象回收实现:
- 主要采用标记清除、标记整理、增量标记算法
- 首先使用标记清除完成垃圾空间的回收
- 采用标记整理对空间优化
- 采用增量标记进行效率优化
细节对比:
- 新生代区域垃圾回收使用空间换时间
- 老年区域垃圾回收不适合复制算法
增量标记如何优化垃圾回收:
让程序跟垃圾回收交替执行
Perfermance
- GC的目的是为了实现内存空间的良性循环
- 良性循环的基石是合理使用
- 时刻关注才能确定是否合理
- Perfermance提供多种监控方式
界定内存问题的标准:
内存泄漏: 内存使用持续增高
内存膨胀:在多数设备中都存在性能问题
频繁垃圾回收:通过内存变化图进行分析
什么是分离DOM:
- 界面元素存活在DOM树上
- 垃圾对象时的DOM节点
- 分离状态的DOM节点
慎用全局变量
- 全局变量定义在全局执行上下文,是所有作用域链的顶端
- 全局执行上下文一直存在于上下文执行栈,直到程序退出
- 如果某个局部作用域出现了同名变量则会遮蔽或污染全局
缓存全局变量
将不可避免的全局变量写在局部作用域中,可以大大提升性能。
通过原型对象添加附加方法
在原型对象上新增实例对象需要的方法