# GC算法,V8引擎

  • GC算法
  • 内存泄露
  • 内存溢出
  • V8引擎
  • performance

# GC算法

# 1. 标记清除

垃圾回收器会在运行时给存储在内存中的所有变量加一个标记,然后去除环境中的变量以及被环境中的变量所引用的变量(闭包)在这些完成后仍存在标记的就是要删除的变量了,因为环境中的变量已经无法访问到这些变量了。缺点是清除的对象可能地址不连续,造成内存碎片化。

优缺点:1. 可处理循环引用 2. 产生碎片化 3. 不会立即回收

你可以想象整个内存是一个大海,每个对象都是一个岛屿,相互之间用大桥连接,现在要找出与大陆相连的岛屿,那么就从大陆出发,依次标记所能达到的每个岛屿,最后没有被标记到的岛屿就是孤岛,可以当垃圾清除掉。即使两个孤岛之间相互有桥连接也没用,因为与大陆不通啊。

# 2. 引用计数

引用计数的策略是跟踪记录每个值被使用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,这个值得引用次数就加一,如果该变量的值变成了另一个,则这个值得引用次数就减一,当这个值的引用次数为0的时候,说明没有变量在使用,这个值无法访问。由此可以将其占用的空间回收,这些垃圾回收器就会在运行时清理掉引用次数为0的值占用的空间,但这种方法容易引起内存泄漏,因为这种方式没有解决循环引用的问题,所以不建议使用!例如:

var obj = { a:1,b:2,c:3 };
obj.o = obj;//循环引用

优缺点:1.即时回收 2. 减少程序卡顿 3.无法解决循环引用 4. 资源消耗大:维护引用计数器

# 3. 标记整理

标记清除的升级版,清除之前先整理空间,再清除,防止内存碎片化

# 什么情况会引起内存泄漏?

虽然有垃圾回收机制但是我们编写代码操作不当还是会造成内存泄漏。

  • 意外的全局变量引起的内存泄漏。

原因:全局变量,不会被回收。

解决:使用严格模式避免。

  • 闭包引起的内存泄漏

原因:闭包可以维持函数内局部变量,使其得不到释放。

解决:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用。

  • 没有清理的DOM元素引用

原因:虽然别的地方删除了,但是对象中还存在对dom的引用

解决:手动删除。

  • 被遗忘的定时器或者回调

原因:定时器中有dom的引用,即使dom删除了,但是定时器还在,所以内存中还是有这个dom。

解决:手动删除定时器和dom。

  • 子元素存在引用引起的内存泄漏

原因:div中的ul li 得到这个div,会间接引用某个得到的li,那么此时因为div间接引用li,即使li被清空,也还是在内存中,并且只要li不被删除,他的父元素都不会被删除。

解决:手动删除清空。

# 内存溢出

  • 内存溢出,申请不到足够的内存;

  • 内存泄露,无法释放已申请的内存;

# V8引擎与垃圾回收策略

  • V8采用即时编译

  • V8内存设限(64操作系统不超过1.5G)

  • 采用分代回收的思想

  • 内存分为新生代,老生代

  • 不同对象采用不同GC算法

# V8内存分配与垃圾回收

V8内存空间一分为二

小空间用于存储新生代对象(32M | 16M)

新生代指的是存活时间较短的对象

新生代内存区分为两个相等空间

使用空间为From,空闲空间为To

活动对象存储于From空间

进行回收时,标记整理将From活动对象拷贝至To,释放From区域

最后From和To交换空间

空间换时间

回收过程中可能出现晋升:

  • 一轮GC还存活的新生代需要晋升到老年代
  • To空间使用率超过25%的需要晋升,因为From和To互换后需要保证新活动对象的空间充足

老生代(1.4G | 700M)

老生代对象就是指存活时间较长的对象

采用标记清除,标记整理,增量标记

首先使用标记清除完成垃圾回收

采用标记整理进行空间优化

采用增量标记进行效率优化,将标记过程分段,与程序执行交替执行,提高效率(垃圾回收会阻塞程序)

# performance

打开F12中的performance工具,点击录制,输入网址并做一些操作,点击停止就可以看到最后一个图的蓝色线条即是内存变换情况。

监控内存的方式

  • 浏览器任务管理器:shift+esc,最后一列可看到JavaScript使用的内存
  • Timeline时序图:蓝色线条,在其上面可定位
  • 堆快照查找分离DOM:F12中的Memory选项,Take snapshot
  • 判断是否存在频繁的垃圾回收:Timeline和任务管理器去查看
最后更新时间: 9/3/2021, 7:07:07 PM