前言
连续两天看到有关垃圾回收机制的提问,脑海中大致对这个概念的了解就是:”程序会定时对于那些不会再使用的内存进行清理“,但具体要谈谈对它的理解,还真的不知道如何说。所以搜索了相关资料,系统地学习了一下。
为什么会有垃圾回收机制的产生?
js程序中,在变量定义的时候会产生二次覆盖,例如:
let test={a:'hello'}
test=[1,2,3,4]
按理来说{a:'hello'}就不会再用到了,但是它还是占据了内存。这样的数据多了的话,最终就会导致内存溢出。
所以产生了垃圾回收机制。
垃圾回收策略(方法)
标记清除算法+标记整理算法(推荐)
标记清除算法:
简单的来说就是,将整个内存看作一个内存列表(已分配+未分配),把所有已分配的内存都打上标记,比如说0,最后从根对象遍历,找出需要清除的对象的内存,然后打上标记(比如说1)。最后遍历内存列表,将标记为1的都清除。
标记整理算法:
在标记清除之前,将需要清除的内容统一整理在一端(需要被清除的内存整合在一起)
引用计数算法(存在漏洞,很少使用)
简单地说就是:对象被引用一次就被计数一次,当它没有被其他对象引用的时候计数就为0。最后GC就清除这些计数为0的内存
缺点:无法解决“循环引用”不能垃圾回收的问题
优点:标记清除算法是隔一段时间才清除,而引用计数算法却可以立即清除
V8对GC的优化
新老生代回收
针对活动对象的内容体积、存在周期,将对象分为(新分配、占用内存周期短、占用空间大)和(很久之前分配、占用空间大、占用周期长)两类。
针对这两类进行不同频率的垃圾回收处理。显而易见,(老,长,大)的对象不需要很频繁的进行垃圾回收处理。
新生代移动到老生代的条件:
1.当使用区中的单个内存占空间25%的时候,就会被放到老生代
2.中,当使用区和空闲区的交换时,某个被多次复制之后任然存在的内存也会被移动到老生代
新生代
新生代被划分为两部分:使用区+空闲区(使用区往往小于空闲区)
空闲区:不用于内存分配
使用区:用于对象的内存分配
操作描述:
针对新生代对象,每次分配内存会在使用区中进行,当使用区占满的时候,就开始GC,对内存进行标记,
然后将所有使用区中的对象全部复制到空闲区,之后在空闲区中对所有对象进行排序(标记整理算法),
最后进行标记清除。然后使用区和空闲区身份互换。
老生代
老生代的清理频次不会那么频繁,它采用标记清除算法+标记整理算法进行定期的垃圾回收。
并行回收+并发回收
针对程序执行效率,js程序是单线程,GC执行时间太长会直接影响到用户体验和整个程序执行的时间过长
并行回收
执行GC的时候,GC分离出辅助线程,为减少执行时间做优化(原本一个人做需要3秒,分成三人做,仅需1秒)。但是这种虽然做到了一定优化,但正对于老生代类型的对象,则效果不明显(由于体积庞大,还是会占用很多时间)
并发回收
GC垃圾回收的时候不占用主线程,GC执行线程与js执行线程相互并行,主线程不受影响。GC单独在后台系统执行。
内存泄漏
垃圾回收机制,对不再使用的内存进行回收。但并不是所有的不可用内存都可以被回收掉的,这种不能被即使回收的内存行为,称为内存泄漏
哪些会产生内存泄漏
1.闭包
2.定时器
3.事件监听器
window.addEventListener("resize", this.doSomething)
最好在组件消除之前清除掉: window.removeEventListener("resize", this.doSomething)
4.console
在开发时用的console在生产环境中需要及时清理
如何排查内存泄漏
借助Chrome Devtool中的Performce + Memory 面板