垃圾回收和运行机制
解释与编译
编译:JAVA语言中,需要先编译成一包代码,之后在运行
解释:js是边编译边执行代码,在解释的时候需要遵守一些规则(js,python)
js是解释型语言,由谁去解释? 浏览器嵌入v8引擎
解释以后由谁来执行? cpu
解释完以后结果如何处理? 渲染至页面
引擎:
- Javascript引擎
- v8(Google底层的javascript引擎)
- javaScriptCore(Apple)
以v8为例子:
- 开始执行js代码
- v8解析源代码并将其转换成AST
- 基于AST,解释器将其转换成字节码
- 同时开始运行代码并收集类型反馈
- 若遇到某些行为经常发生则发送至优化编译器(内联缓存技术)
- 若检测到行为不正确,则优化编译器将取消优化,并回到解释器
complier
AST
通过parser转换:
程序的本质是什么:源代码
源代码:相当于一段字符串
AST:将字符串通过编译器编译成结构型的数据,也就是抽象语法树
上述代码执行的第一步:通过 parser(转换器)将js代码转换成如上图所示的抽象语法树
垃圾回收(称为GC)
做垃圾回收的原因: 程序都是临时存储在计算机的内存中,通过cpu去执行计算的,计算机的内存⛽️大小限制的,当程序占用过大会导致电脑直接卡死。
引用计数法(不使用)
let a = {name:'lili',age:12}
a=[1,2,3]
//上述首先开辟了新的内存空间存放:{name:'lili',age:12}
//在给a赋值为[1,2,3]时候又开辟了一块新的内存空间,存放[1,2,3]
//知识点:引用类型的赋值相当于复制了一个新的指针,这个指针指向对堆中存放新赋值的对象
可达性:是否可以被访问到,若无法被访问,则会被回收
何时被标记为垃圾?
如上图例:
当第一次为a赋值的时候,内存空间中的对象引用计数设置为1,当第二次为a赋值的时候,内存空间中的对象已经没有任何东西去引用他了,此时将其标记为0。进行垃圾回收时候,当发现引用计数为0的时候就会对变量进行回收。
缺点:当出现循环引用时,无法进行回收,会一直被引用,内存被一直占用
function test(){
let A = new Object() +1 +1 = +2 -1 = 1
let B = new Object() +1 +1 = +2 -1 = 1
A.b = B
B.a = A
A = null
B = null
}
优点:清晰简单
缺点:1、循环引用无法回收 2、开辟计数器占空间
标记清除算法(常用)
- 垃圾收集器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为0
- 然后从各个根对象开始遍历,把不是垃圾的节点改成1
- 清理所有标记为0的垃圾,销毁并回收它们所占用的内存空间
- 最后,把所有内存中对象标记修改为0,等待下一轮垃圾回收,垃圾清扫的过程是逐步进行的
优点
标记清除算法的优点只有一个,那就是实现比较简单,打标记也无非打与不打两种情况,这使得一位二进制位(0和1)就可以为其标记,非常简单
缺点 内存不连续,变成内存碎片
解决办法: 标记清除整理算法:
过程: 一旦发现内存中有垃圾节点会进行内存整理
问题:内存为什么要连续呢?提升内存的利用率,分配速度慢
内存管理
运行机制
浏览器事件循环
事件循环:在处理异步函数的时候,并不是立马入栈就出栈,而是先入放到任务队列中,等到合适的机会在进行出栈。
-
宏任务
可以将每次执行栈执行的代码当做是一个宏任务
- I/O:数据输入输出
- setTimeout
- setInterval
- setImmediate
- requestAnimationFrame
-
微任务
当宏任务执行完,会在渲染前,将执行期间所产生的所有微任务都执行完
- process.nextTick
- MutationObserver
- Promise.then
- catch
- finally
-
整体流程
- 执行一个
宏任务(栈中没有就从事件队列中获取) - 执行过程中如果遇到
微任务,就将它添加到微任务的任务队列中 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)- 当前
宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染 - 渲染完毕后,
JS线程继续接管,开始下一个宏任务(从事件队列中获取)
console.log(1);
queueMicrotask(() => {console.log(2)}); //queueMicrotask将函数转成微任务
Promise.resolve().then(() => console.log(3));
setTimeout(() => {console.log(4)})