JavaScript 性能优化、内存管理、垃圾回收

JavaScript性能优化

什么属于性能优化

1.提高运行效率的行为
2.降低运行开销的行为

学习点

  • 内存管理
  • 垃圾回收(GC:Garbage Collection)及一些算法
  • V8引擎垃圾回收
  • 内存监控:chrome开发工具Performance

内存管理

  • 内存:由可读写单元组成,表示一片可操作空间
  • 管理:人为的去操作一片空间的申请、使用和释放
  • 内存管理:开发者主动申请空间、使用空间、释放空间

JavaScript中的内存管理

EcmaScript并没有提供操作内存的API,所以JS不能像其他语言(C、C++)一样,由开发者主动调用API,来完成空间的管理(申请、使用、释放操作)。

依然可以演示内存空间的声明周期

// 申请
// JS自动为变量分配内存空间
let obj = {}

// 使用
// 对变量的读写操作
obj.name = 'Jack'

// 释放
// 利用JS垃圾回收机制完成释放
obj = null

注:上例obj会被垃圾回收机制(GC)自动回收,当因为特殊调用而未被GC回收时,可通过=null对其进行释放。

JavaScript垃圾回收

  • JS中内存管理是自动的

JavaScript中的垃圾

  • 对象不再被引用时是垃圾
  • 对象不能从根上访问到时是垃圾

JavaScript中的根可以理解为是全局变量对象或全局的执行上下文环境

可达对象

  • 可以访问到的对象就是可达对象(引用、作用域链)
  • 可达的标准就是从根出发,是否能够被找到

引用和可达

let obj = {
  name: 'Jack'
}
// {name: 'Jack'}被obj引用,当前引用次数1
// global.obj是可达的
// global.obj.name是可达的

let ali = obj
// {name: 'Jack'}被ali引用,当前引用次数2
// global.ali是可达的
// global.ali.name是可达的

obj = null
// {name: 'Jack'}减少一次引用,当前引用次数1

GC算法

  • GC就是垃圾回收机制的简写
  • GC可以找到内存中的垃圾、并释放和回收空间
  • 算法就是工作时查找和回收所遵循的规则

常见GC算法

  • 引用计数
  • 标记清除
  • 标记整理(V8)
  • 分代回收(V8)

引用计数

  • 核心思想:设置引用数,判断当前引用数是否为0
  • 引用计数器:引用关系改变时修改引用数字
  • 引用数字为0时立即回收

优点

  • 监听对象引用数,发现垃圾时立即回收
  • 最大限度减少程序暂停(当内存被占满时,GC就会立即寻找垃圾进行释放)

缺点

  • 无法回收循环引用的对象
function fn () {
  const obj1 = {}
  const obj2 = {}
  obj1.name = obj2
  obj2.name = obj1
}
fn()
// 根据引用计数算法,obj1和obj2由于循环引用,引用数值永远不为0,GC无法回收obj1和obj2
  • 资源开销大、耗时(引用计数需要监控维护引用数值的变化,对象越多,需要维护的数值也越多,相比其他GC算法更耗时)

标记清除

  • 核心思想:标记和清除两个阶段
  • 标记:遍历堆内存中的所有对象,(从根开始)找到活动对象(可达对象),对其进行标记
  • 清除:遍历堆内存中的所有对象,清除没有标记的对象,然后清除所有的标记(下次GC工作时重新标记)
  • 触发:当程序运行期间,若可以使用的内存被耗尽时,GC线程就会将程序暂停开始工作,先将依旧存活的对象标记一遍,再将堆中没有被标记的对象全部清除,最后让程序恢复运行。
function fn () {
  const obj1 = {}
  const obj2 = {}
  obj1.name = obj2
  obj2.name = obj1
}
fn()

// 假设此时内存被占满/耗尽,触发GC工作
// 引用计数判断obj1和obj2还在被引用,无法回收
// 标记清除从根上访问不到obj1和obj2,不对其标记,清除阶段回收
// 上面所说的obj1和obj2指的是它们所指向的存储在堆内存中的数据

优点

  • 相对引用计数,解决对象循环引用不能回收的问题

缺点

  • 空间碎片化:标记清除回收的内存空间,在地址上是不连续的,分散在各个角落。不能让空间最大化的使用
  • 由于不连续,GC会将回收的内存单元存放到一个空闲内存列表中,对这个列表的维护也是一种开销。
  • 不会立即回收垃圾对象,工作时程序是暂停的

标记整理

  • 标记清除的增强,减少碎片化空间
  • 不同于标记清除,标记整理在清除阶段,会先整理移动对象的位置,让它们在地址上产生连续,然后再进行回收
  • 不会立即回收垃圾对象,工作时程序是暂停的

V8

  • 主流的JavaScript执行引擎,内存管理机制,高效
  • V8采用即时编译
  • V8内存设限:64位操作系统不超过1.5G,32位操作系统不超过800M
    • 对于web应用来说足够
    • 这个上限使垃圾回收时,不会令回收时间超过用户的感知

原始类型数据由JS语言自身管理。

存放在堆区的对象类型数据由内存进行管理,GC进行回收。

V8常用GC算法

  • 分代回收
  • 空间复制
  • 标记清除
  • 标记整理
  • 增量标记

V8分代回收

V8内存空间是有上限的,所以它按照一定规则分成两个空间,分别存放新生代和老生代对象,针对不同代采用适合的GC算法

新生代

  • 小空间存储新生代对象,空间大小:64位32M,32位16M
  • 新生代指的是存活时间较短的对象,例如局部变量,在函数执行完就会被回收
新生代对象回收实现

回收过程采用复制算法+标记整理

  1. 新生代内存也会区分为两个等大的空间,分别为使用空间From、空闲空间To
  2. 为活动对象分配空间时,先存储于From空间
  3. 当From空间应用的一定的程度(占满)之后,就会触发GC操作
  4. 使用标记整理的算法对活动对象进行标记和整理
  5. 然后将被标记的对象拷贝至To空间
  6. 回收时清空From,From与To进行调换,完成释放
晋升

新生代对象满足一定标准就会被晋升(晋升就是将对象移至老生代):

  1. 一轮GC还存活的新生代需要晋升
  2. 拷贝过程中,To空间的使用率超过25%,就会晋升之后拷贝的活动对象
新生代的意义

优化垃圾回收(GC)的性能

  1. 简化了新对象的分配(只在新生代分配内存)
  2. 可以更有效的清除不再需要的对象(新老生代使用不同的GC算法)

研究发现:

  1. 很多对象的生存时间都很短。
  2. 新生对象很少引用生存时间长的对象。

结合以上两点,很明显GC会频繁访问新生对象,例如新生代空间。在新生代中,GC可以快速标记回收“死对象”,而不需要扫描整个Heap中的存活一段时间的“老对象”。

老生代

  • 空间大小:64位1.4G,32位700M
  • 老生代对象指存活时间较长的对象,例如全局变量、闭包
老生代对象回收实现

采用标记清除、标记整理、增量标记算法

  1. 首先使用标记清除完成垃圾空间的回收
  2. 当新生代对象晋升时(对象从新生代移动到老生代),老生代中没有足够的空间去存放,就会触发标记整理对碎片化的空间进行整理回收。
  3. 采用增量标记进行效率优化
增量标记

GC触发标记清除回收时,程序会暂停,标记清除算法一口气标记全部活动对象,相对耗时太久。

增量标记将标记过程根据直接可达对象和间接可达对象(例如obj.foo和obj.foo.bar),将一整个过程拆分成多个小过程,每个过程指标记一层可达对象,标记完继续执行程序,然后再执行下个标记小过程。

这样对用户来说,程序执行更连贯。

新生代 VS 老生代

  • 新生代区域垃圾回收使用空间换时间
  • 老生代区域垃圾回收不适合复制算法(对象太多,复制耗时)

内存监控

监控工具 performance

performance是W3C引入的web API。接口可以获取当前页面中与性能相关的信息。

可以获取的信息:

  1. 白屏时间:从打开网站到有内容渲染出来的时间节点
  2. 首屏时间:首屏内容渲染完毕的时间节点
  3. 用户可操作的时间节点:domready触发节点
  4. 页面总下载时间:window.onload的触发节点
  5. DNS查询时间
  6. TCP链接时间

使用:

  • 可以通过只读属性window.performance获得
  • 可以通过浏览器开发者工具使用

更多查看监控工具 performance

内存问题的体现

  • 页面的性能随时间延长越来越差(内存泄漏)
  • 页面持续性出现糟糕的性能(内存膨胀)
  • 页面出现延迟加载或经常性暂停(频繁垃圾回收)

界定内存问题的标准

  • 内存泄漏:内存使用持续升高
  • 内存膨胀:在多数设备上都存在性能问题
    • 当前应用程序本身为了达到最优的效果,需要分配很大的内存空间。也许由于硬件不支持造成。需要在多个设备上测试,判断是程序问题还是设备问题。
  • 频繁垃圾回收:通过内存变化图进行分析

常见内存监控的方式

  • 浏览器任务管理器
  • Timeline时序图记录
  • 堆快照查找分离DOM(分离DOM:内存泄漏)
  • 借助不同工具获取当前内存的走势图,进行一个时间段的分析,从而判断是否存在频繁的垃圾回收

任务管理器监控内存

Shift+Esc调出浏览器自带的任务管理器

  • 内存:DOM节点所占用的内存
  • Javascript内存:JS使用的堆内存
    • 括号中的内存:所有可达对象正在使用的内存

可达对象使用的内存不断增加,说明内存使用是有问题的。

Timeline 时间线记录内存变化

Heap Snapshot 堆快照查找分离DOM

堆快照留存JS堆照片

分离DOM

  • 界面元素(DOM节点)存活在DOM树上,DOM节点存在两种形态:垃圾对象和分离DOM
  • 垃圾对象:脱离了DOM树,并且JS中也没有被引用
  • 分离状态的DOM节点:脱离了DOM树,但JS中还被引用。

分离DOM占据内存空间,这就是种内存泄漏,可通过堆快照查找分离DOM,从而在代码中进行清除。

判断是否存在频繁GC

为什么确认是否存在频繁垃圾回收

  • GC工作时应用程序是停止的
  • 频繁且过长的GC会导致应用假死
  • 用户使用中感知应用卡顿

如何确认频繁的垃圾回收

  • Timeline中频繁的上升下降
  • 任务管理器中数据频繁的增加减小
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值