垃圾回收与运行机制

126 阅读4分钟

垃圾回收和运行机制

解释与编译

编译:JAVA语言中,需要先编译成一包代码,之后在运行

解释:js是边编译边执行代码,在解释的时候需要遵守一些规则(js,python)

js是解释型语言,由谁去解释? 浏览器嵌入v8引擎

解释以后由谁来执行? cpu

解释完以后结果如何处理? 渲染至页面

引擎:

  1. Javascript引擎
  • v8(Google底层的javascript引擎)
  • javaScriptCore(Apple)

以v8为例子:

  1. 开始执行js代码
  2. v8解析源代码并将其转换成AST
  3. 基于AST,解释器将其转换成字节码
  4. 同时开始运行代码并收集类型反馈
  5. 若遇到某些行为经常发生则发送至优化编译器(内联缓存技术)
  6. 若检测到行为不正确,则优化编译器将取消优化,并回到解释器

complier

image.png

AST

通过parser转换: image.png

程序的本质是什么:源代码

源代码:相当于一段字符串

AST:将字符串通过编译器编译成结构型的数据,也就是抽象语法树

image.png image.png 上述代码执行的第一步:通过 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、开辟计数器占空间

标记清除算法(常用)

  1. 垃圾收集器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为0
  2. 然后从各个根对象开始遍历,把不是垃圾的节点改成1
  3. 清理所有标记为0的垃圾,销毁并回收它们所占用的内存空间
  4. 最后,把所有内存中对象标记修改为0,等待下一轮垃圾回收,垃圾清扫的过程是逐步进行的

优点

标记清除算法的优点只有一个,那就是实现比较简单,打标记也无非打与不打两种情况,这使得一位二进制位(0和1)就可以为其标记,非常简单

缺点 内存不连续,变成内存碎片

image.png

解决办法: 标记清除整理算法:

过程: 一旦发现内存中有垃圾节点会进行内存整理

image.png

问题:内存为什么要连续呢?提升内存的利用率,分配速度慢

内存管理

运行机制

浏览器事件循环

事件循环:在处理异步函数的时候,并不是立马入栈就出栈,而是先入放到任务队列中,等到合适的机会在进行出栈。

  1. 宏任务

可以将每次执行栈执行的代码当做是一个宏任务

  • I/O:数据输入输出
  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame
  1. 微任务

宏任务执行完,会在渲染前,将执行期间所产生的所有微任务都执行完

  • process.nextTick
  • MutationObserver
  • Promise.then
  • catch
  • finally
  1. 整体流程

  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
console.log(1);

queueMicrotask(() => {console.log(2)}); //queueMicrotask将函数转成微任务

Promise.resolve().then(() => console.log(3));

setTimeout(() => {console.log(4)})