一些名词的解释
什么是性能优化?
提高运行效率,降低运行开销的行为都可以看做性能优化。
js语言本身的优化,实现编写高效率的代码。
什么是内存管理?
内存:由可读写单元组成,表示一片可操作空间
管理:人为的去操作一片空间的申请、使用和释放
内存管理:开发者主动申请空间、使用空间、释放空间
管理流程:申请 -> 使用 -> 释放
js的内存空间在定义变量时自动分配,程序猿无法指定明确大小
js执行平台虽然存在GC机制,但是由于不同算法的限制,代码书写不同同样会导致内存无法回收,导致内存泄露。
js内存生命周期
- 申请内存空间: let obj = {};
- 使用内存空间: (读写操作) obj.name = 'sunny';
- 释放内存空间: (js中并没有相应的释放api) obj = null;
js中的垃圾回收
js中的内存管理是自动的,每当我们创建函数、对象、数组的时候会自动的分配相应的内存空间;
- 对象不再被引用的时候是垃圾;
- 对象不能从根上访问到时也是垃圾;
js可达对象
在一个作用域链上,只要通过根可以有路径查找到的对象都是可达对象。
可以访问到的对象就是可达对象(可以通过引用、作用域链查找到)
可达的标准就是从根出发是否能够被找到
js中的根就可以理解为全局变量对象
js中的引用是如何体现的?
// 引用
let obj1 = {name: 'xm'};
let al1 = obj1;
obj1.age = 26;
obj1 = null;
console.log(al1, obj1); // { name: 'xm', age: 26 } null
GC算法
GC是什么?
垃圾回收机制的简写。可以找到内存中的垃圾,并释放和回收垃圾。
GC中的垃圾是什么?
- 程序中不再需要使用的对象
- 程序中不能再访问到的对象
GC算法是什么?
GC是一种机制,垃圾回收器完成具体的工作。
- 工作的内容就是查找垃圾释放空间、回收空间。
- 算法就是工作时查找和回收所遵循的规则。
常见的GC算法
引用计数
- 实现原理
内部通过引用计数器,来维护当前对象的引用数,从而判断该对象的引用数是否为0,来决定它是否是一个垃圾对象。如果引用数值为0,GC就开始工作,将其所在的内存空间进行回收释放和再使用
- 引用计数器
当某一个对象的引用关系发生改变时,引用计数器就会自动的修改这个对象所对应的引用数值(比如我们代码中有一个对象空间,一个变量引用了它,那么这个对象空间的引用数值加1) 引用数值为0的时候GC就开始工作,将当前的对象空间回收
- 引用计数的优点
可以即时回收垃圾对象、减少程序卡顿时间。
- 发现垃圾立即回收(因为根据当前的引用数值是否为0判断他是否是一个垃圾,如果是垃圾就回收内存空间,释放内存)
- 最大限度的减少程序暂停(应用程序在执行的过程中必定会对内存进行消耗,而当前的执行平台内存空间是有上限的,所以内存肯定会有占满的时候。由于引用计数算法时刻监控着那些引用数值为0的对象,当内存爆满的时候会去找那些引用数值为0的对象释放其内存,这个也就保证了当前的内存空间不会有占满的时候)
- 引用计数的缺点
无法回收循环引用的对象、资源消耗较大
- 无法回收循环引用的对象
- 时间开销大(当前的引用计数需要去维护一个数值的变化,时刻监控当前引用数值是否修改,修改需要时间)
标记清除
- 实现原理
核心思想就是将整个垃圾回收操作分为两个阶段:
- 遍历所有的对象找到活动对象,进行标记的操作
- 遍历所有的对象,找到那些没有标记的对象进行清除。(注意在第二阶段中也会把第一阶段涉及的标志给抹掉,便于GC下次能够正常的工作)
通过两次的遍历行为把我们当前的垃圾空间进行回收,最终交给我们的空闲列表进行维护。
- 总结
核心思想:分标记和清除两个阶段:
- 遍历所有的对象找活动对象做标记
- 遍历所有的对象清除没有标记的对象
- 回收相应空间
- 优点
可以回收循环引用的对象空间。相对于引用计数算法来说:解决对象循环引用的不能回收问题。
- 缺点
容易产生碎片化空间,浪费空间、不能立即回收垃圾对象。
空间碎片化:所谓空间碎片化就是由于当前所回收的垃圾对象,在地址上面是不连续的,由于这种不连续造成了我们在回收之后分散在各个角落,造成后续使用的问题
标记整理
实现原理
和标记清除一样,在V8中也会被频繁使用。
- 标记整理可以看做是标记清除的增强;
- 标记阶段的操作和标记清除一致;
- 清除阶段之前会先执行整理,移动对象位置,使得他们在地址上是一个连续的空间
- 优点
减少碎片化空间
- 缺点
不会立即回收垃圾对象
function func() {
name = '123456';
return `my name is ${name}!`;
}
func();
console.log(name);
// 对象之间循环引用案例
function fn1() {
const obj1 = {};
const obj2 = {};
obj1.name = obj2;
obj2.name = obj1;
return 'lg is a code!';
}
fn1();
代码解释:
调用fn1()后其内部所占用的内存空间会回收释放,比如我们的obj1 和 obj2,因为在全局的地方已经没有引用指向obj1/obj2的情况了,所以引用计数为0,当回收obj1、obj2时,发现obj2的属性指向obj1,obj1的属性指向obj2,有着互相的指引关系,所以引用计数数值不为0,所以这一块空间也释放不了。