这里是《图解 Google V8》第三篇/共三篇:事件循环与垃圾回收这里主要说2点:事件循环:宏任务和微任务微任务和微任务的执行时机是什么垃圾收集垃圾收集运行过程垃圾收集算法通过本专栏学习,V8不再是一个陌生的黑盒,它变成了一个熟悉的黑盒,因为本专栏可以让你了解V8的大体原理,面试时吹牛还是可以的,但仅此而已,详情还需深入17|MessageQueue:V8是如何实现回调函数的?同步回调函数在执行函数内部执行。异步回调函数在执行函数外执行。UI线程就是运行窗口的线程,也叫主线程。当鼠标点击页面时,系统会将事件交给UI线程。处理,但UI线程不能立即响应处理这种情况。浏览器为UI线程提供了一个消息队列,然后UI线程会不断从消息队列中获取事件并执行事件。如果没有消息等待处理,那么这个循环会被setTimeout暂停,setTimeout会被执行。浏览器会将回调函数封装成一个事件,并添加到消息队列中。然后UI线程会不间断的从消息队列中取出任务并执行任务。取出setTimeout回调函数XMLHttpRequest,在UI线程上执行XMLHttpRequest,会阻塞UI线程,所以UI线程会把它分配给网络线程(网络进程中的一个线程):UI线程从中取出任务消息队列,分析发现是一个下载任务,会交给网络线程去执行。网络线程收到下载请求后,会与服务器建立联系,发送下载请求。网络线程会不断地从服务器接收数据。回调函数封装成一个事件,放在消息队列中,UI线程循环读取消息队列。如果是下载状态的事件,UI线程会执行回调函数,直到下载事件结束,页面显示下载完成。18|异步编程(一):V8是如何实现microtasks的?宏任务是消息队列中等待主线程执行的事件。当执行每个宏任务时,都会创建一个堆栈。当宏任务结束时,堆栈也会被清空。微任务是需要异步执行的函数。microtasks在函数执行后,当前macrotask结束前的执行时机:如果在当前task中产生了microtask,则不会在当前function中执行,所以执行microtask时,不会导致堆栈的无限扩展任务将在当前任务执行结束之前执行。在微任务执行结束前,不会执行其他任务。参考资料V8Promise源码综合解读JavaScript事件循环与NodeJS事件循环19|异步编程(二):V8是如何实现async/await的?带星号的Generator函数可以和yield一起使用,实现函数的暂停和恢复。这称为生成器函数*getResult(){console.log("getUserIDbefore");产生“getUserID”;console.log("getUserNamebefore");产生“getUserName”;console.log("之前的名字");return"name";}letresult=getResult();console.log(result.next().value);console.log(result.next().value);console.log(result.next().value);在生成器内部,如果遇到yield关键字,V8会将yield之后的内容返回给外部,并暂停函数生成器的执行,暂停后,外部代码开始执行。如果想继续恢复generator的执行,可以使用result.next()方法来切换pause和resume。这背后的原理就是协程,比线程更轻量级的是运行在线程上的任务。一个线程有多个协程,但只能执行一个协程。如果A协程启动了B协程,那么A协程就是B协程的父协程。编写同步代码function*getResult(){letid_res=yieldfetch(id_url);控制台日志(id_res);让id_text=yieldid_res.text();控制台日志(id_text);letnew_name_url=name_url+"?id="+id_text;console.log(new_name_url);让name_res=yieldfetch(new_name_url);控制台日志(name_res);让name_text=yieldname_res.text();console.log(name_text);}letresult=getResult();result.next().value.then((response)=>{returnresult.next(response).value;}).then((response)=>{returnresult.next(response).value;}).then((response)=>{returnresult.next(response).value;}).then((response)=>{returnresult.next(response)。价值;});将执行generator代码的函数调用为executor(参考著名的co框架)function*getResult(){letid_res=yieldfetch(id_url);控制台日志(id_res);让id_text=yieldid_res.text();控制台日志(id_text);让new_name_url=name_url+"?id="+id_text;安慰。日志(新名称_url);让name_res=yieldfetch(new_name_url);控制台日志(name_res);让name_text=yieldname_res.text();console.log(name_text);}co(getResult());async/awaitasync是一个异步执行并隐式返回Promise作为结果的函数。await后面可以跟两种表达式:任意普通表达式Promise对象表达式如果await等待一个Promise对象,它会暂停生成器函数的执行,直到Promise对象的执行变为resolve,然后将resolve的值作为await表达式的运算结果{returnnewPromise((resolve,reject)=>resolve("resolve"));}asyncfunctiongetResult(){leta=awaitNeverResolvePromise();控制台日志(一);//不会输出}asyncfunctiongetResult2(){letb=awaitResolvePromise();控制台日志(b);//"解析"}getResult();getResult2();console.log(0);async是异步执行的函数,不会阻塞主线程执行async函数是一个独立的协程,执行时可以用await暂停。因为它在等待一个Promise对象,所以它可以通过resolve恢复。模型原理与协原理20|垃圾收集(一):V8的两个垃圾收集器是怎么工作的?通过GCRoot标记内存中的活动对象和非活动对象。V8使用reachability算法来判断堆中的对象是否是活动对象GCRoot可以遍历的对象。是可达的,称为活动对象GCRoot无法遍历的对象。可访问(unreachable),称为inactiveobjects浏览器环境中有很多GCRootwindow对象(位于每个iframe中)DOM,它由遍历document可以到达的所有nativeDOM节点存储栈上的变量来回收inactiveobjects占用的内存被回收后,做内存碎片整理(可选,有些垃圾收集器不会产生内存碎片,比如二级垃圾收集器)。回收后,内存中会出现大量不连续的空间,如果在里面,称为内存碎片如果碎片太多,当需要大的连续内存时,就会出现内存不足的情况。V8受代际假说的影响,使用了两个垃圾收集器:主垃圾收集器(MajorGC)和次垃圾收集器(Minorgarbagecollector)。GC)代际假说:大多数对象都是“生死存亡”的,也就是说,大多数对象在内存中的生存时间很短,比如在函数内部声明的变量,或者块级作用域中的变量,当一个函数或块时代码执行时,作用域中定义的变量被销毁。因此,这类对象一旦被分配内存,很快就会变得不可访问。不朽的对象会活得更久,比如:window、DOM、WebAPI等。V8将堆分为两个区域:新生代:存放生命周期短对象容量小,1~8M使用二级垃圾收集器(MinorGC)并使用Scavenge算法将新生代区域分为两部分Objectarea(from-space)Freearea(to-space)Objectarea用于存放新加入的object当objectarea快满时,进行垃圾清理(markfirst,然后清理),将活动对象复制到空闲区,排序(空闲区不会有内存碎片)。复制完成后,翻转对象区域和自由区域重复以上步骤。将两次垃圾回收后仍然存在的对象移至老年代。老年代:存放长寿命对象,容量大,对象占用空间大。存活时间长的大对象使用主垃圾收集器(MajorGC)标记-清除算法(Mark-Sweep)标记:从根元素开始,找到活动对象,如果找不到,就是垃圾清理:直接清理垃圾(会产生垃圾碎片)或者使用标记-压缩算法(Mark-Compact)标记:从根元素开始,找到活动对象,找不到的就是垃圾排序:移动活动对象到同一端,另一端直接清理(不会产生垃圾碎片)。参考资料深入了解Node.js中的垃圾收集和寻找内存泄漏了解Node.js:核心思想和源代码分析JavaScript垃圾收集器何时以及如何工作V8引擎内存管理垃圾回收(二):V8如何优化垃圾回收器的执行效率?JavaScript在主线程上运行。一旦执行了垃圾回收算法,JavaScript就会暂停执行,并在垃圾回收完成后恢复执行。此行为已成为全局线程。暂停(Stop-The-World)V8团队在现有垃圾收集器的基础上增加了并行、并发和增量垃圾收集技术。这些技术主要从两个方面解决垃圾回收效率问题:拆分一个完整的垃圾回收任务拆分多个小任务,将标记对象、移动对象等任务交给后端线程并行回收(在主线程中执行,全停顿)。当主线程执行垃圾回收任务时,启动多个辅助线程同时进行回收工作采用并行收集,垃圾回收消耗的时间=辅助线程数*单个线程消耗的时间在这个过程中执行垃圾标记后,主线程不会同时执行JavaScript代码,因此代码不会改变收集流程。假设内存状态为Static,那么只要确保只有一个辅助线程同时访问该对象即可。这是V8子垃圾收集器采用的策略。将对象空间中的数据移动到空闲区域。这个操作会导致数据地址改变,所以也需要同步更新这些对象的指针。增量回收(在主线程中执行,穿插在任务之间)将标记工作分解为更小的块,穿插在主线程的不同任务之间进行增量回收。垃圾收集器不需要一次完成垃圾收集过程。它每次只执行一小部分工作。增量收集是并发的,需要满足两个需求:垃圾收集可以随时暂停和重启。暂停期间,需要保存扫描结果,等待下一次采集。在暂停期间,如果标记的垃圾数据被修改,垃圾收集器需要正确处理。垃圾回收时,V8采用三色标记方式:黑色:表示所有可以访问的数据(活跃数据),子节点已经被标记白色:表示还没有被访问过,如果到最后还是白色一轮遍历后,这个数据会被回收灰色:表示这个节点正在处理,子节点还没有处理。垃圾回收器会根据是否有灰色节点来判断本轮遍历是否结束。垃圾清理是灰色的:一轮遍历还没有结束,标记为黑色的数据是从灰色节点修改过来的,也就是说黑色节点引用了一个白色节点,但是黑色节点已经被标记了,这是因为它后面有一个白色节点不会被标记为黑色。这需要一个约束:黑色节点不能指向白色节点。约束是:写屏障机制(Write-barrier):当黑色节点引用白色节点时,写屏障机制会强制白色节点变为灰色,从而保证黑色节点不能指向白色节点。这种方法称为强三色不变性。并发回收(不在主线程执行)在主线程执行JavaScriptpt,辅助线程在后台进行垃圾回收操作。优点:不会挂起主线程(JavaScript可以自由执行,辅助线程可以同时进行垃圾回收),但是有两点难以实现:当主线程执行JavaScript时,堆中的内容会随时发生变化,这会使辅助线程之前的工作无用武之地。主线程和辅助线程可能同时修改同一个对象,这就需要额外实现读写锁功能。学习笔记系列《图解 Google V8》设计思路-学习笔记(一)《图解 Google V8》编译流程-学习笔记(二)
