v8垃圾回收机制,内存垃圾回收机制

  v8垃圾回收机制,内存垃圾回收机制

  本文带你了解一下V8引擎的内存管理和垃圾收集算法,希望对你有所帮助!

  众所周知,JS自动管理垃圾回收,开发者不需要关心内存分配和回收。而且垃圾收集机制也是前端面试常见的部分。本文主要解释V8的逐代垃圾收集算法。希望看完这篇文章的朋友们能对V8的垃圾收集机制有一个透彻的了解(哈哈,就是!),文章主要涵盖以下内容:

  V8的内存限制及解决方法。新一代内存对象清理算法是基于可达性分析算法来标记幸存对象的逻辑和优化手段。新一代内存对象的提升条件,Scavenge算法的深度/广度优先区分跨代内存的写障碍。老一代内存对象的标记和清理/排序算法GC STW原因及优化策略

V8的内存限制与解决办法

   V8最初是为浏览器设计的。使用大内存的情况很少。默认情况下,设计中对内存使用有限制,只允许使用部分内存。64位系统允许使用的内存约为1.4g,32位系统允许使用的内存约为0.7g,如以下代码所示,查看Node中依赖V8引擎的内存限制方法:

  process . memory sage();

  //返回以字节为单位的内存使用量。

  {

  rss: 22953984,

  //请求的总堆内存

  heapTotal: 9682944,

  //已用堆内存

  heapUsed: 5290344,

  外部:9388

  }

  V8限制内存使用大小还有一个重要原因。当堆内存过大时,V8执行垃圾回收需要很长时间(1.5g需要50ms),做非增量垃圾回收需要更长时间(1.5g需要1s)。后续解释完V8的垃圾收集机制,相信大家能更感同身受。

  虽然V8发动机对内存使用有限制,但是修改内存限制的同样方法是在启动V8发动机时添加相关参数。以下代码演示了如何修改节点中的相关V8引擎内存限制:

  #更改老一代的内存限制,以mb为单位

  node - max-old-space-size=2048

  #更改新一代的内存限制,单位为mb

  node-max-semi-space-size=1024=64 index . js这里需要注意的是,改动后的新一代内存的语法已经改成了上面的写法,单位也从kb变成了mb。旧的写法是node-max-new-space-size。您可以通过以下命令查询当前节点环境以修改新一代内存的语法:

  节点-V8-选项 grep最大值

  

V8垃圾回收策略

  在引擎自动垃圾收集机制的历史演进中,已经发现没有一个通用的算法可以解决任何场景下的垃圾收集问题。所以现代垃圾回收算法是根据对象的存活时间对内存垃圾进行分代分代垃圾回收算法是针对不同类型的内存垃圾实现不同的回收算法。

  V8将内存分为新生代和老一代:

  新一代内存中对象的生存期短;老一代内存中对象的生存期长;或者内存驻留中的新一代内存存放在新一代内存空间(半空间),老一代内存存放在老一代内存空间(旧空间),如下图所示:

  新一代存储器采用清除算法,老一代存储器采用标记-清除和标记-压缩算法。我们来看看Scavenge的算法逻辑!

  :新一代内存的内存回收采用

Scavenge算法

   Scavenge算法,具体实现Scavenge采用Cheney算法。切尼算法把新一代内存空间分为两部分,一部分空间在用(FromSpace),另一部分空间闲置(称为ToSpace)。

  分配内存时,首先在FromSpace中分配。当执行垃圾收集机制时,将检查FromSpace中的存活对象,并将存活对象复制到to ToSpace,释放非存活对象占用的空间。复制完成后,FromSpace和ToSpace的角色将是翻转。当一个物体经过多次复制后仍然活着,就被认为是长期有生命的物体。这时会出现晋升,然后将对象移到老一代oldSpace,用新的算法管理。

  Scavenge算法实际上是在两个空间中来回复制活体,这是一种典型的以空间换时间的方式,所以非常适合新生代内存,因为复制的只是活体,新生代内存中的活体是少数。但是有几个重要问题需要考虑:

  引用避免重复拷贝假设有三个对象temp1、temp2和temp3,其中temp1由temp2和temp3引用。js代码示例如下:

  变量温度2={

  ref: temp1,

  }

  变量温度3={

  ref: temp1,

  }

  Var temp1={}将temp2从FromSpace复制到ToSpace时,当发现引用了temp1时,将temp1复制到ToSpace是一个递归过程。但是,当我复制temp3时,我发现temp1也被引用了。此时,重复复制TEMP3。

  为了避免重复复制,方法是在复制时给对象加上一个“已访问”的标记,表示该节点已经被访问过,然后通过已访问的属性来决定是否复制对象。

  拷贝后保持正确的引用关系还是上面说的参考关系。因为temp1不需要重复复制,所以temp3在复制到ToSpace后,并不知道temp1对象在ToSpace中的内存地址。

  方法是当复制temp1时,对象节点上会生成一个新的field属性指向新的内存空间地址,并更新为旧内存对象的forwarding属性,这样temp3就可以通过旧temp1的forwarding属性在ToSpace中找到引用地址。

  记忆同时存在于新生代和老一代之后,这也带来了问题:

  内存对象跨代(跨空间)后如何标记常量温度1={}

  恒定温度2={

  ref: temp1,

  }比如上面代码中的两个对象temp1和temp2存在于新一代中,其中temp2指的是temp1。假设temp2在GC之后提升到老一代,那么在GC的下一个标记阶段,如何判断temp1是否是活体呢?

  在基于可达性分析的算法中,要知道temp1是否是活的,需要知道是否存在引用temp1对象的根对象引用。在这种情况下,年轻一代GC将遍历所有老一代对象,以确定是否存在引用temp1对象的根引用对象,因此生成算法没有意义。

  解决方案是维护所有跨代引用的记录集记录,这是一个写缓冲区列表。每当旧代中的内存对象指向新代中的内存对象时,旧代中该对象的内存引用都会记录在记录集中。因为这种情况一般发生在对象写操作中,顾称之为写屏障,还有一种可能的情况是发生在推广过程中。记录的维护只能涉及对象的写入和提升。这带来了另一个问题:

  每次写操作时维护记录集的额外开销优化是指在一些曲轴操作中不需要写屏障,堆栈上的内存对象也不需要写屏障。还有其他的,更多的手段这里就不多讨论了。

  缓解Scavenge算法内存利用率不高问题幸存对象在新一代内存中的比例比较小,所以在分配空间的时候,ToSpace可以少分配一点。方法是将ToSpace空间分为S0和S1两部分,S0作为ToSpace,S1作为FromSpace与原FromSpace合并。

  

Scavenge算法中深度/广度优先的区别

  在垃圾收集算法中,一般有两种机制来识别一个内存对象是否是垃圾:引用计数基于可达性分析

  基于可达性分析,就是找出所有根引用(如全局变量等。),遍历所有根引用,递归引用根引用上的所有引用。所有被遍历的引用都是存活对象并被标记。此时,空间中的所有其他内存对象都是死对象,从而构造了一个有向图

  考虑到递归的限制,递归逻辑一般采用非递归实现,常见的有广度优先和深度优先算法。两者的区别在于:

  当深拷贝到ToSpace时,内存对象的顺序被改变,有引用关系的对象靠得更近。原因是你复制了自己之后,直接复制了自己引用的对象,所以相关对象在ToSpace中更接近深度。相反,由于CPU的缓存策略,在读取内存对象时,有很大概率会一起读取后面的对象,以便更快地命中缓存。因为代码开发时常见的场景是obj1.obj2.obj3,所以CPU读取obj1时,如果同时读取后面的obj2和obj3,有利于命中缓存。

  因此,深度优先算法更有利于业务逻辑命中缓存,但其实现依赖于额外的堆栈辅助实现算法,消耗内存空间。相反,广度优先虽然不能提高缓存命中率,但它的实现可以巧妙地利用指针避免空间消耗,算法执行效率高。

  

新生代内存对象的晋升条件

  新一代内存对象要提升到老一代需要满足以下条件:

  无论一个对象是否经历了清除回收,ToSpace的内存使用率都不能超过限制。判断其是否经历过Scavenge GC的逻辑是,每次GC时给存活对象赋予年龄属性1,再次GC时再判断年龄属性。的基本促销图如下:

  在老一代内存中,有很多存活了很长时间的对象,不能采用Scavenge算法进行回收的原因如下:

  存活的对象太多,导致复制效率低下,浪费了一半的内存空间

老生代内存对象的回收算法

  旧内存空间的垃圾收集采用Mark-Sweep和Mark-Compact相结合的方式。清晰标记分为两部分:

  在标记阶段,清理阶段(如果是标记整理,就是整理阶段)遍历旧堆内存中的所有内存对象,标记活的对象。在清理阶段,只清理未标记的对象。原因是:老一代人的记忆中有少数非生命体。

  如上图所示,标记清洗的一个问题是清洗后有不连续的空格,不能连续使用。因此,旧内存空间的内存清理需要结合标记整理的方案。该方案是在标记过程中将活体移动到一边,移动完成后将所有非活体清理出界并移走。

  

垃圾回收的全暂停

  应用程序执行逻辑需要在垃圾收集期间暂停,然后在垃圾收集机制结束后恢复。这种行为被称为“全暂停”,也就是通常所说的停止世界,简称STW。新一代内存的垃圾回收对应用执行的影响很小,而老一代内存对老一代内存垃圾回收造成的总暂停影响很大,因为存活的对象很多。

  为了优化GC的总暂停时间,V8还引入了增量标记、并发标记、并行标记、增量整理、并行清理和延迟清理。

  

STW优化

  衡量垃圾收集所用时间的一个重要指标是执行GC时主线程挂起的时间。STW的影响是无法接受的,所以V8也采取了很多优化措施。

  并行GCGC的过程需要做很多事情,这就导致了主线程上的STW现象。并行GC的做法是打开多个辅助线程来共享GC。这种方法仍然不能避免STW现象,但它可以减少STW的总时间,这取决于打开的工作线程的数量。

  增量式GC增量式GC将GC工作拆分,并在主线程中间歇执行。这样不会减少GC的时间,但是会有一点点代价,但是也会减少GC的总STW时间。

  并发GC并发GC是指GC在后台运行,不再在主进程中运行。这将避免STW现象。

  空闲时间GCChrome中动画的渲染约为60帧(每帧约16ms)。如果当前渲染时间达到16.6ms,那么就会有空闲时间去做其他事情,比如一些GC任务。

  

减少垃圾回收的影响

  为了提高执行效率,我们应该尽量减少垃圾收集的执行和消耗:

  小心翼翼地把内存当缓存,小心翼翼地把对象当缓存,合理限制到期时间和无限增长的问题。可以采用lru策略。

  避免使用内存在节点中存储用户会话。否则,在内存中存储大量用户会话对象会导致老一代内存激增,影响清理性能,进而影响应用程序执行性能和内存溢出。改进redis的使用方式等。将缓存转移到外部的好处:

  减少内存驻留对象的数量,提高垃圾收集的效率,并在进程间共享缓存知识。请访问:nodejs教程!以上就是说一下V8的内存管理和垃圾回收算法的细节。请多关注我们的其他相关文章!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: