🗒️gc垃圾回收机制
type
status
date
slug
summary
tags
category
icon
password
Blocking
Blocked by
top
URL
Sub-item
Parent item
垃圾回收策略
标记清除
分为
标记
和 清除
两个阶段,标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为0
然后从各个根对象开始遍历,把不是垃圾的节点改成1,清理所有标记为0的垃圾,销毁并回收它们所占用的内存空间。最后,把所有内存中对象标记修改为0,等待下一轮垃圾回收
引用计数
一个对象,如果没有其他对象引用到它,这个对象就是
零引用
,将被垃圾回收机制回收它的策略是跟踪记录每个变量值被使用的次数
一个对象被其他对象引用时,这个对象的引用次数就为 1,如果同一个值又被赋给另一个变量,那么引用数加 1,如果该变量的值被其他的值覆盖了,则引用次数减 1
当这个值的引用次数变为 0 的时候,说明没有变量在使用,这个值没法被访问了,回收空间,垃圾回收器会在运行的时候清理掉引用次数为 0 的值占用的内存
分代式垃圾回收机制
V8采用了一种代回收的策略,将内存分为两个生代:新生代和老生代
新生代中的对象为存活时间较短的对象,老生代中的对象为存活时间较长或常驻内存的对象
内存回收的例子
假设代码中有一个对象 jerry ,这个对象从创建到被销毁,刚好走完了整个生命周期,通常会是这样一个过程
1)这个对象被分配到了新生代;随着程序的运行,新生代塞满了,GC 开始清理 新生代里的死对象,jerry 因为还处于活跃状态,所以没被清理出去;
2)GC清理了两遍新生代,发现 jerry 依然还活跃着,就把 jerry 移动到了老生代
3)随着程序的运行,老生代也塞满了,GC 开始清理老生代,这时候发现 jerry 已经没有被引用了,就把 jerry 给清理出去了。
新老生代垃圾回收方式
新老生代采用不同的垃圾回收算法来提高效率,对象最开始都会先被分配到新生代(如果新生代内存空间不够,直接分配到老生代),新生代中的对象会在满足某些条件后,被移动到老生代,这个过程也叫晋升
新生代的垃圾回收方式
将内存空间一分为二,分为From空间(使用状态), To空间(闲置状态)
当新生代内存不足时,会将From空间中存活的对象复制到到To空间,然后将From空间清空,交换From空间和To空间(将原来的From空间变为To空间),继续下一轮
老生代的垃圾回收方式
V8在老生代中主要采用了Mark-Sweep和Mark-Compact相结合的方式
Mark-Sweep遍历堆内存中的所有对象,并标记活着的对象,在随后的清除阶段,只清除没有被标记的对象
Mark-Sweep最大的问题就是,在进行清除回收以后,内存空间会出现不连续的状态,会造成内存碎片化
Mark-Compact用来解决内存碎片的问题,将将存活对象向内存一侧移动,清空内存的另一侧,这样空闲的内存都是连续的
内存泄露
最常见的情况就是异步任务的回调未清除或者定时器任务没有清楚,其他的还有DOM引用未清除等。
如何排查内存泄露
通过开发者工具中的
Performance
模块,可以对页面加载和执行流程进行录制,之后在录制快照里查看内存的使用情况。如果发现有内存异常,则可以进一步缩小排查范围,最终找到引发内存泄露的代码。