内存管理

内存为什么需要管理

function fn(){
    arrlist=[];
    arrlist[100000]='lg is a coder';
}
fn();

image.png

不当的操作会导致堆内存,飙升。
内存管理介绍:

  1. 内存:可读写单元组成,表示一片可操作空间
  2. 管理:人为的去操作一片空间的申请,使用和释放
  3. 内存管理:开发者主动申请空间,使用空间,释放空间
  4. 管理流程:申请-使用-释放

javascript中的垃圾回收

  1. javascript中内存管理是自动的
  2. 对象不再引用时是垃圾
  3. 对象不能从根上访问到时是垃圾
javascript中的可达对象
  • 可以访问到的对象就是可达对象(引用,作用域链)
  • 可达的标准就是从根触发是否能够被找到
  • javascript中的根就可以理解为是全局变量对象
javascript中的引用和可达

image.png
obj假如是某个函数返回的
里面的o1和o2互相引用

通过作用域链引用可以找到的可达对象。

image.png
如果把对o1的相关引用去掉,他就无法被找到,就变成了一个不可达对象,垃圾。
image.png

GC算法介绍

GC的定义与作用
  • GC就是垃圾回收机制的简写
  • GC可以找到内存中的垃圾,并释放和回收空间
GC里的垃圾是什么
  • 程序中不再需要使用的对象
  • 程序中不能再访问到的对象(不可达对象)
GC算法是什么

GC是一种机制,垃圾回收器完成具体的工作
工作内容就是查找垃圾释放空间,回收空间
算法就是工作时查找和回收所遵循的规则

常见GC算法
  • 引用计数
  • 标记清除
  • 标记整理
  • 分代回收

引用计数算法实现原理

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

优点

  • 发现垃圾时立即回收(实时监控引用数,为0立即回收)
  • 最大程度减少程序卡顿(实时监控,实时回收释放内存,某种意义上保证内存不会被占满)

缺点

  • 无法回收循环引用的对象(计数一直不为0)
  • 时间开销大,资源消耗较大(多维护了计数器,时刻维护计数器,维护计数器的过程需要时间)
标记清除算法实现原理
  • 核心思想:分标记和清除二个阶段
  • 遍历所有对象标记活动对象
  • 遍历所有对象清除没有标记对象
  • 回收相应空间

例如
image.png
内存里有这些对象,GC工作时会递归找到可达对象,图中a-d,c-e,进行标记
image.png
然后遍历所有清除不可达对象,(a1和b1找不到属于不可达对象,未被标记),这个a1和b1引用计数就回收不了因为引用一直存在,循环引用。

标记清除的优缺点

优点:

  • 最大优点可以清除循环引用(上例中就可以清除,因为是不可达对象,未被标记)

缺点:

  • 不会立即回收垃圾对象,不像引用计数实时监控,第一次遍历对象发现不可达对象,也无法进行清除,只能是先标记,第二次再清除,回收。
  • 会造成空间碎片化,如图:

image.png

不可达对象释放到空闲链表里的时候,因为有中间可达对象并没有被释放,所以他们是不连续的[域1,域2,...,域1],这样会造成空间碎片化,假设说每个域为1, 在我们需要2.5个内存时,这片空间就无法满足了。

标记整理算法实现原理
  • 标记整理可以看作是标记清除的增强
  • 标记阶段的操作和标记清除一致
  • 清除阶段会先执行整理,移动对象位置

image.png
image.png
image.png

优点

  • 减少碎片化空间

缺点

  • 不会立即回收垃圾对象
  • 移动对象位置,回收效率慢

认识V8

  • v8是一款主流jsvascript执行引擎
  • v8采用即时编译
  • v8内存设限(64位不超1.5G,32位不超800M,因为1本身是针对浏览器的,现有大小对网页应用来说足够了,2,内部垃圾回收机制也决定了采用这设置是非常合理的,管方做过测试,当垃圾内存达到1.5G时,采用增量标记的算法回收,只需要消耗50ms,非增量标记回收1秒钟,从用户体验来说1秒已经算是很长了,所以就以1.5G为界限了(变大消耗时间会变长)。)
v8垃圾回收策略
  • 采用分代回收的思想
  • 内存分为新生代,老生代
  • 针对不同对象采用不同的算法

image.png

v8常用GC算法
  • 分代回收
  • 空间复制
  • 标记清除
  • 标记整理
  • 标记增量
v8如何回收新生代对象

image.png

  • v8内存空间一分为二(新生代/老生代)
  • 小空间用于储存新生代对象(62位32M/32位16M)
  • 新生代指的是存活时间较短的对象
新生代对象回收实现

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

  1. 新生代内存区分为两个等大小空间
  2. 使用空间From,空闲空间为To
  3. 活动对象储存于From空间中
  4. 触发回收操作时,先标记整理From空间将活动对象和不可达对象都进行整理,把不可达对象整理成连续的(空间规整)),然后将活动对象复制拷贝至To。
  5. 然后From与To交换空间,完成释放(这回原来的From就成了To,原来的T哦就成了From)

回收细节说明:

  1. 拷贝过程中可能出现晋升
  2. 晋升就是将新生代对象移至老生代
  3. 一轮GC还存活的新生代需要晋升
  4. To空间的使用率超过25%晋升(防止交换空间后新生代内存不够使用)
老生代对象回收
老年代对象说明
  • 老年代对象存放在右侧老生代区域
  • 64位操作系统1.4G,32操作系统700M
  • 老年代对象就是指存货时间较长的对象
老年代对象回收实现
  • 主要采取标记清除,标记整理,增量标记算法
  • 正常使用标记清除完成垃圾空间的回收(主要使用标记清除,虽然会有一些碎片,但是相对来说速度很明显提升)
  • 采用标记整理进行空间优化(使用时机,新生代对象往老生代区域移动时且这个时间节点上,老生代区域空间又不足以来存放新生代移过来的对象,这时就会触发标记整理,把之前的碎片化空间整理来存放)
  • 采用标记增量进行效率优化。

image.png
标记增量如图,会分段执行标记回收工作,让使用者尽量感受不到程序的暂停,即使时最后的清除阶段也不会让使用者感觉到卡顿。

细节对比
  • 新生代区域垃圾回收使用空间换时间
  • 老生代区域垃圾回收不适合复制算法(因为本来空间就比较大,里面存放的对象也多,划分俩空间的话首先是浪费,其次因为对象多,复制操作得不偿失。)

代码调试(性能检测)

Performance工具介绍

使用Performance时刻监控内存
内存问题的体现

  • 页面出现延迟加载或经常性暂停
  • 页面持续性出现糟糕的性能
  • 页面性能随时间越长越来越差

界定内存问题的标准

  • 内存泄漏:内存使用持续升高,不见释放
  • 内存膨胀:在多数设备都存在性能问题。(指需要的内存,硬件设备无法提供)
  • 频繁垃圾回收:通过内存变化图进行分析
监控内存的几种方式
浏览器任务管理器(shift+esc)右击调出jsvascript所占内存image.png

第一个内存是dom节点内存,持续增长证明在创建节点,后面javascript内存是可达对象内存,持续增长证明我们在创建对象,如果一直增长不见下降,那就是有内存泄漏发生,如果频繁 飙高降低,证明在频繁GC

Timeline时序图记录

image.png
测试一下,我使用的是Edge,打开f12性能模块,谷歌里是Performance差异是中英文差异,然后点击录制
image.png
然后进行对界面操作,之后点击停止,会有你这一段时间操作过程的内存以及浏览器重绘的过程记录,在这里包括也能刷新看到界面一个加载过程,可以看到那里导致加载慢
image.png
频繁点击按钮,这里就不断创建大的字符串放到数组里,可以看到内存的升高,降低是因为浏览器的GC,如果时序图这里内存一直升高不释放,证明有内存泄漏了(可以找到对应时间节点查看),如果一直频繁飙高降低,这也是不正常的,证明在频繁的GC。(原始类型是放在栈上的,引用类型是放在堆上的)

什么是分离DOM
  • 垃圾对象时的dom节点(从dom上脱离,js里也没有引用时的dom)
  • 分离状态的dom(从dom树上脱离,但是js里有引用不释放,内存泄漏了)
堆快照查找分离dom

用代码演示下
image.png
这样界面没有,但是js引用有,这就是分离dom,我们看下如何查找这种dom。
image.png
找到Edge内存模块
image.png
先直接获取快照,方便对比 搜索detached 发现没有分离dom
然后点击按钮 再从用户配置获取新的快照,搜索deta,发现我们创建的分离dom
image.png
这里就可以看到分离的dom和一些它的相关信息,如果想要释放的话tepEle手动赋值null就可以了

判断是否存在频繁垃圾回收
  • GC工作时程序是停止的,
  • 频繁过长的gc会导致应用假死
  • 用户使用感知卡顿

频繁GC判断
timeline中频繁上升下降
浏览器任务管理中数据频繁增大或减小

https://jsbench.me/
JSBench 用来测试代码执行速度

image.png
放入对比代码然后run 可以查看执行效率
也可以放入一些前置dom或者js代码。(测试所需)

经过一些测试有一些运行较快的总结如下:
避免全局变量,全局变量特点:
**挂载在window下
至少一个引用数
存活更久,但持续占用内存**

避免全局查找
全局查找相关:

  • 目标变量不存在当前作用内,通过作用域链向上查找
  • 减少全局查找降低时间消耗
  • 减少不必要全局变量定义
  • 全局变量数据局部化(必须要全局的话,可以缓存全局)

避免循环引用(全局引用指多个对象间存在互相引用)

采用字面量代替new操作

setTimeout替换setInterval

采用事件委托

合并循环变量和条件

在原型对象上新增实例对象需要方法

不为了闭包而闭包。


Charon
57 声望16 粉丝

世界核平