javaScript 引擎中有一个后台进程称为垃圾回收器,它监视所有对象,并删除那些不可访问的对象。
(1) Js垃圾回收机制:
Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。
(2) 内存泄漏:
指当前不需要使用的内存还存着,这种情况就叫做内存泄漏
(3)垃圾回收原理:
找到那些不在使用的变量,释放其占用内存,垃圾回收器会按照固定的时间间隔周期性的执行这一个操作,垃圾回收器各种变量,并打上标记,以备回收。
(4)回收方式
1、标记清除:现在主流回收机制
2、可达性:指已某种方式可访问或者可用的值,被保证储存在内存中
3、工作原理: 执行环境中,进入环境的变量,都是不可回收,离开环境的变量,从当前引用或引用链可以上访问任何其他值,则认为该值是可访问的。否则被标识为不可达,即为可回收资源,缺点: 碎片化内存
4、引用计数: 如果引用或引用链可以从根访问任何其他值,则认为该值是可访问的。机制就是跟踪一个值的引用次数,当声明一个变量并将一个引用类型赋值给该变量时该值引用次数加1,当这个变量指向其他一个时该值的引用次数便减一。当该值引用次数为0时就会被回收
5、引用计数的缺点: . 如果引用或引用链可以从根访问任何其他值,则认为该值是可访问的。
6标记整理: 和标记清除相似,再回收之前,先进行内存整理,把碎片化内存整理成一整块大的内存
7优化:
(1)分代回收:
(2)增量回收: 如果有很多对象,并且我们试图一次遍历并标记整个对象集,那么可能会花费一些时间,并在执行中会有一定的延迟。因此,引擎试图将垃圾回收分解为多个部分。然后,各个部分分别执行。这需要额外的标记来跟踪变化,这样有很多微小的延迟,而不是很大的延迟。
(3)空闲时间回收: 垃圾回收器只在 CPU 空闲时运行,以减少对执行的可能影响。
(5) V8回收机制:
- 1、限制:V默认情况下,64位环境下的V8引擎的新生代内存大小32MB、老生代内存大小为1400MB,而32位则减半,分别为16MB和700MB。V8内存的最大保留空间分别为1464MB(64位)和732MB(32位)。具体的计算公式是4*reserved_semispace_space_ +ax_old_generation_size_,新生代由两块reserved_semispace_space_组成,每块16MB(64位)或8MB(32位),V8最初为浏览器而设计,不太可能遇到用大量内存的场景,层原因是,V8的垃圾回收机制的限制(如果清理大量的内存垃圾是很耗时间,这样回引起JavaScript线程暂停执行的时间,那么性能和应用直线下降),GC释放的是堆中的内存,
- 2、支持算法: 新生代使用Scavenge GC 算法,Cheney算法,老生代使用标记清除算法和标记整理算法,增量标记算法,利用间隙完成标记
- 3、流程:首先把新生代内存分配成等大的两个部分,From和TO两个,首先分配到From,当From内存分配万,执行Scavenge GC算法,把From中存活的对象Copy到To,把From所有对象全部清除掉,将From和To对调,当新生代中的对象经过两个周期还存活的将移到老生代中,或者To内存使用>=25%,也将对象移到老生代,使用标记清理和标记整理进行清理内存
- 4、新生代过程: 新生代使用Scavenge算法进行回收。在Scavenge算法的实现中,主要采用了Cheney算法。Cheney算法算法是一种采用复制的方式实现的垃圾回收算法。它将内存一分为二,每一部分空间称为semispace。在这两个semispace中,一个处于使用状态,另一个处于闲置状态。处于使用状态的semispace空间称为From空间,处于闲置状态的空间称为To空间,当我们分配对象时,先是在From空间中进行分配。当开始进行垃圾回收算法时,会检查From空间中的存活对象,这些存活对象将会被复制到To空间中(复制完成后会进行紧缩),而非活跃对象占用的空间将会被释放。完成复制后,From空间和To空间的角色发生对换。也就是说,在垃圾回收的过程中,就是通过将存活对象在两个semispace之间进行复制。可以很容易看出来,使用Cheney算法时,总有一半的内存是空的。但是由于新生代很小,所以浪费的内存空间并不大。而且由于新生代中的对象绝大部分都是非活跃对象,需要复制的活跃对象比例很小,所以其时间效率十分理想。复制的过程采用的是BFS(广度优先遍历)的思想,从根对象出发,广度优先遍历所有能到达的对象
- 5、标记清除和标记整理都分为两个阶段:标记阶段、清除或紧缩阶段 在标记阶段,所有堆上的活跃对象都会被标记。每个内存页有一个用来标记对象的位图,位图中的每一位对应内存页中的一个字。这个位图需要占据一定的空间(32位下为3.1%,64位为1.6%)。另外有两位用来标记对象的状态,这个状态一共有三种(所以要两位)——白,灰,黑: 如果一个对象为白对象,它还没未被垃圾回收器发现 如果一个对象为灰对象,它已经被垃圾回收器发现,但其邻接对象尚未全部处理 如果一个对象为黑对象,说明他步进被垃圾回收器发现,其邻接对象也全部被处理完毕了 如果将堆中的对象看做由指针做边的有向图,标记算法的核心就是深度优先搜索。在初始时,位图为空,所有的对象也都是白对象。从根对象到达的对象会背染色为灰色,放入一个单独的双端队列中。标记阶段的每次循环,垃圾回收器都会从双端队列中取出一个对象并将其转变为黑对象,并将其邻接的对象转变为灰,然后把其邻接对象放入双端队列。如果双端队列为空或所有对象都变成黑对象,则结束。特别大的对象,可能会在处理时进行分片,防止双端队列溢出。如果双端队列溢出,则对象仍然会成为灰对象,但不会被放入队列中,这将导致其邻接对象无法被转变为灰对象。所以在双端队列为空时,需要扫描所有对象,如果仍有灰对象,将它们重新放入队列中进行处理。标记结束后,所有的对象都应该非黑即白,白对象将成为垃圾,等待释放 清除和紧缩阶段都是以内存页为单位回收内存 清除时垃圾回收器会扫描连续存放的死对象,将其变成空闲空间,并保存到一个空闲空间的链表中。这个链表常被scavenge算法用于分配被晋升对象的内存,但也被紧缩算法用于移动对象 紧缩算法会尝试将碎片页整合到一起来释放内存。由于页上的对象会被移动到新的页上,需要重新分配一些页。大致过程是,对目标碎片页中的每个活跃对象,在空闲内存链表中分配一块内存页,将该对象复制过去,并在碎片页中的该对象上写上新的内存地址。随后在迁出过程中,对象的旧地址将会被记录下来,在迁出结束后,V8会遍历所有它所记录的旧对象的地址,将其更新为新地址。由于标记过程中也记录了不同页之间的指针,这些指针在此时也会进行更新。如果一个页非常活跃,如其中有过多需要记录的指针,那么地址记录会跳过它,等到下一轮垃圾回收进行处理