最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 老生常谈,垃圾回收♻️

    正文概述 掘金(介似麻鸭)   2021-04-30   728

    前言

    读完这篇文章你能了解什么❓

    在JavaScript中内存管理是自动的,在开发中我们不需要自己手动分配内存,释放内存。那干嘛看这篇文章?

    当然是学思想和方法啦,难道是为了装逼13吗。技术都是互通的,懂的原理对性能优化也是有好处滴。

    • V8引擎的垃圾机制是如何工作的
    • 新一代垃圾收集器Orinoco如何减少垃圾回收的阻塞时间
    • 学到一些性能优化的思想

    为什么要进行垃圾回收

    因为电脑的内存不是无限的,如果不进行垃圾回收,内存就会炸掉,这就像像地球?的资源一样(可持续发展直呼内行)。

    垃圾回收的作用就是只留下运行时必要的内存,其它不用的垃圾统统清理掉,留到后续要分配内存的时候使用。

    老生常谈,垃圾回收♻️

    垃圾回收痛点

    众所周知,JavaScript是单线程的。为保证数据同步,在进行垃圾回收时,最简单的办法就是暂停JavaScript的执行,然后GC开始工作,完成之后再继续执行JavaScript。

    这么做势必会造成页面的卡顿,如:用户操作不响应,页面动画不连续。

    在V8的老一代的垃圾回收机制中,采用的就是这种做法。而在谷歌V8团队研发的新一代垃圾回收器(代号:Orinoco)中,就是针对进行垃圾回收时会造成页面卡顿这个痛点进行优化。Orinoco做了很多优化手段,为的就是尽可能的减少垃圾回收对主线程的阻塞,使用户感觉不到页面的卡顿。

    老生常谈,垃圾回收♻️

    垃圾回收基本原理

    任何垃圾回收机制都会周期性的执行一些基本步骤:

    1. 找到哪些是垃圾(凉凉的对象,dead objects),哪些不是垃圾(活动对象,live objects)
    2. 回收再利用那些垃圾占用的内存
    3. 压缩/整理垃圾留下的碎片(可选)

    这些任务可以按顺序进行,也可以任意交错进行。

    V8中的GC是如何工作的

    实际上,在V8中,有两个垃圾回收器,分别是

    • Major GC(主GC),又名Full Mark-Compact
    • Minor GC(次GC),又名Scavenger

    每个GC工作在不同的阶段。

    主GC工作流程

    主GC是通过遍历整个堆内存来进行垃圾回收。主GC在标记(marking)、清除(sweeping)和压缩(compacting)三个阶段进行工作。

    标记?

    标记是的区分垃圾对象和活动对象的其中一种方式。先来说说另一种方式:引用计数

    引用计数

    在老一代的浏览器中,是使用引用计数来区分当前对象是不是垃圾。

    当引用计数为0时,这个对象就是个垃圾,当引用计数不为0时,这个对象就是活动对象。

    举个例子?

    let obj = { a: 1 }; // obj具有这个对象的引用,引用计数为1,不是垃圾
    
    obj = null; // 引用没了,引用计数为0,恭喜{ a:1 }?现在是一名合格的垃圾了
    

    但是这个方法有个致命的问题,循环引用

    老生常谈,垃圾回收♻️

    举个例子?

    let obj1 = {}; // obj1具有这个对象的引用,这里称为对象a,对象a的引用计数为1
    let obj2 = {}; // obj1具有这个对象的引用,这里称为对象b,对象b引用计数为1
    obj1.foo = obj2; // obj1.foo具有对象b的引用,对象b引用计数为2
    obj2.foo = obj1; // obj2.foo具有对象a的引用,对象a引用计数为2
    

    现在把obj1和obj2都干掉

    obj1 = null;
    obj2 = null;
    

    然后现在对象a和对象b还是互相引用的,引用计数永远为1,这个垃圾就永远清理不掉

    老生常谈,垃圾回收♻️

    标记

    由于引用计数有bug,所以现在的浏览器GC使用的大部分用的是标记清除法(mark-and-sweep)。

    其中标记是GC工作的一个重要步骤。GC会从几组根指针开始往下找,直到把运行时的对象全部打上标记为止。找到的对象(reachable objects)就标记为活动对象,没找到的对象就标记为垃圾。

    • 当前函数的局部变量和参数。
    • 嵌套调用时,当前调用链上所有函数的变量与参数。
    • 全局变量。
    • (还有一些内部的)

    清除

    清除就是把标记为垃圾的对象全部干掉。

    先来一张标记加清除一起工作的图:

    老生常谈,垃圾回收♻️

    再看看主GC具体的怎么做的:

    主GC会将垃圾占用的内存添加到一个叫**空闲列表(free-list)**的数据结构中。在完成标记操作之后,GC就会找到相连的”垃圾“们,将他们添加到合适的空闲列表中。空闲列表的内存块通过内存块大小分割,方便查找。当需要重新分配内存的时,只需去空闲列表中找一个大小合适的内存块。

    老生常谈,垃圾回收♻️

    压缩

    主GC会使用启发式算法对部分页面页面进行压缩,这个过程有点像window的磁盘清理功能。

    老生常谈,垃圾回收♻️

    GC会将一个页面中的活动对象的复制到另一个未进行压缩处理的页面,从而最大化利用内存空间。

    在复制活动对象的时候,有个隐患,如果这个对象的寿命很长,那会复制这些对象会付出很高昂的代价(使用中的对象的值随时可能变化,要是在这个过程复制移动对象,很有可能会出现找不到当前对象等不可预知的问题)。所以主GC只会选择那些高度碎片化的压面进行压缩操作,其它页面只会选择进行清理操作

    老生常谈,垃圾回收♻️

    次GC工作流程

    世代布局

    在说次GC工作流程的时候,先说说世代布局的概念。

    在V8中使用的堆会被分成几个区域,这些个区域被称为世代(generations),世代分为年轻一代(young generation)和老一代(old generation),年轻一代又进一步分为幼儿园(nursery)和中学生(intermediate)。

    • 一开始对象会在分配到幼儿园区域;
    • 如果在幼儿园经历了一轮GC操作后没被干掉,这个对象就会移动到中学生区域;
    • 如果再经历一轮GC还没被干掉,就会移动到老一代中。

    这么说有点抽象,来一张图:

    老生常谈,垃圾回收♻️

    世代假说

    在垃圾回收机制中有个重要的概念叫世代假说,这个假说说的就是大部分对象都活不久。在GC看来,很多对象基本上在刚声明不久就无法访问了。比如,你在函数中声明的对象,这个函数一执行完,里面的声明的对象马上就访问不到了。这个概念不是V8或者JavaScript独有的,任何动态语言都适用。

    V8的世代布局就充分的利用世代假说这个事实。虽然在GC工作的时候复制对象成本很高,但是根据世代假说,我们知道实际上能活下来的只是一小部分的对象。存活不久的放在年轻一代中,存活得久的转移到老一代中,然后使用不同的GC进行操作,区别对待。其实V8支付的成本只是和这长久活下来的一小部分对象成正比的,而不是声明的多少对象就花费多少成本。

    工作流程

    上面有说到,V8有两个垃圾清理器,主GC是在整个堆上工作的,而次GC是在年轻一代上工作的。

    因为根据世代假说,新分配的对象寿命很短?,很需要垃圾回收。

    次GC主要做三个步骤:标记(marking)、撤离(evacuating)、指针更新(pointer-updating)。这三个步骤是在不同阶段交替进行的。标记之前说了这里就不重复了。

    撤离

    主GC是通过启发式算法对部分高度碎片化的页面(压缩)进行碎片清理工作,而次GC则是使用撤离的方式进行碎片清理。

    V8为年轻一代使用一个叫semi-page的设计,为了做撤离操作,总空间中有一半是空的。总空间被分为两部分,一开始空的那个空间叫To-Space,需要复制的地方叫From-Space,其实就是对应幼儿园和中学生。

    首先,次GC会将所有活动对象撤离到一个连续的内存空间中,从而完成碎片清理工作。

    老生常谈,垃圾回收♻️

    接着,GC会调换From-Space和To-Space,新分配的对象会先添加到From-Space最下面,然后马上撤离到To-Space。

    老生常谈,垃圾回收♻️

    更新指针

    这样换下去,很快空间就会用完,所有在第二轮GC的时候会把剩下的活动对象迁移到老一代中,最后更新一下原来指针的指向。

    老生常谈,垃圾回收♻️

    Orinoco

    好了,终于说到我们的重点了,原来的垃圾处理机制是会阻止主线程的运行直到垃圾回收的整个过程完成,衡量垃圾回收性能的一项重要指标是GC执行时主线程的暂停时间。我们现在来看看Orinoco做了哪些优化。

    Orinoco主要使用并行(parallel)、增量(incremental)和并行(concurrent)这三种技术来释放主线程。

    老生常谈,垃圾回收♻️

    并行

    并行就是指主线程和辅助线程在同一个时间做大致等量的工作。这虽然还是会阻塞主线程,但是阻塞的时间会减少,总阻塞时间会变成原来的n(线程的总数)分之一。

    这是三种技术中最简单的一种。由于这时候js是停止执行的,所以只需要解决各个辅助线程在访问同一个对象时的数据同步问题即可。

    老生常谈,垃圾回收♻️

    增量

    增量是指主线程间歇性的执行一小部分工作。把整个GC操作切成一小块一小块的,每次执行GC的一小部分工作。这种方式要难很多,因为JavaScript执行是夹杂在增量中的,所以很有可能在因为堆状态的变化,导致之前的一个增量工作无效。

    从下图可以看出,这并不会减少GC任务的总执行时间,甚至稍微会增加一点。但是这是一个减少JavaScript阻塞时间的好方法。通过允许JavaScript间歇性的运行可以保证程序在GC操作中依然可以响应用户的输入和动画的执行。

    老生常谈,垃圾回收♻️

    并发

    并发指的是完全不阻塞JavaScript执行,GC的步骤全部通过辅助线程在后台执行。

    这是三种技术中最难的一个,在任何时间JavaScript堆的任何东西都有可能改变,导致之前做的清理工作无效。主线程和辅助线程还有可能同时读取和修改相同的对象,会存在read/write races的问题

    并发的好处是除去一点点和辅助线程同步的开销,可以完全释放主线程去执行JavaScript。

    老生常谈,垃圾回收♻️

    Orinoco是如何将三种优化的技术运用到GC的各个阶段的❓

    在次GC工作的时候(Scavenging)

    在年轻一代GC工作的时候,V8会使用并行的方式将清理工作分布到多个辅助线程和主线程之中。

    每个线程会分别收到几个指针,然后将这些指针指向的活动对象撤离到To-Space。在撤离时,这些清理任务必须使用原子的读入、写入、比较和交换操作来保证数据同步。

    在一个辅助线程工作的时候,另一个线程可能通过其它路径先找到了相同的对象,并试图移动这个对象。不管是哪个线程先找到这个对象,都会更新这个对象的指针地址并返回。它会留下一个转发指针给其它线程告知这个对象更新完之后的地址是什么。

    老生常谈,垃圾回收♻️

    在主GC工作的时候(Major GC)

    主GC使用并发技术进行标记和清理操作,使用并行技术进行压缩和指针更新操作。

    在V8中主GC是通过并发的标记开始的。当堆接近动态计算的临界点时,并发标记任务就会开始。整个并发标记都发生在后台,不影响JavaScript主线程的运行。

    V8会使用Write barriers记录在并行标记期间生成的新对象的引用。

    具体步骤:

    • 每个辅助线程都会被分配几个指针,然后根据这个指针去找,给找到的对象打上标记。

    • 当并发标记完成,或者到达动态分配的临界值时,主线程会进行一个很快的标记收尾步骤。

    • 主线程就这这个时候被GC暂停。然后主线程再次从根节点开始扫描,确保标记了所有的活动对象,然后和几个辅助线程开始并行执行压缩和指针更新操作。

    • 清理工作在主线程暂停期间开始并行工作,不影响JavaScript执行。

    不是所有旧空间(old-space)里的页面都需要进行压缩操作,那些没有压缩的页面会使用之前提到的空闲列表进行清理。

    老生常谈,垃圾回收♻️

    闲暇时间的GC(Idle-time GC)

    JavaScript没有直接可以操作GC的方法。但是V8给宿主环境提供了触发GC的操作。GC会发送一些闲暇任务(Idle Tasks),就算宿主不主动触发这些任务,这些任务也会被GC自动执行。

    像谷歌浏览器这种宿主会有一些空闲时间的概念。谷歌浏览器每秒会刷新60次,每刷新一次(一帧)占用的时间大概是16.6ms,在这16.6ms内的动画如果提前执行完成了,剩下的时间就是闲暇时间,可以用来执行GC的闲暇任务。

    GC会利用主线程上的空闲时间来主动执行GC工作。利用空闲时间,每次执行一点点??,其实就是增量

    老生常谈,垃圾回收♻️

    总结

    1. V8中有两个GC,一个是主GC,在整个堆上工作,另一个是次GC,在年轻一代上工作。
    2. 由于大部分对象活动时间不长(世代假说),所以V8将内存堆分成了几个区域,不同的GC在不同额区域进行不同的处理。
    3. 主GC会执行标记、清除和压缩三个步骤。次GC会执行标记、撤离、指针更新三个步骤。
    4. 新一代GC(Orinoco)使用并行、增量和并发技术减少GC对主线程的阻塞时间。
    5. 次GC使用并行技术进行撤离操作。主GC使用并发技术进行标记和清理操作,使用并行技术进行压缩和指针更新操作。

    参考:


    起源地下载网 » 老生常谈,垃圾回收♻️

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元