内存管理
内存为什么需要管理
function fn(){
arrlist=[];
arrlist[100000]='lg is a coder';
}
fn();
不当的操作会导致堆内存,飙升。
内存管理介绍:
- 内存:可读写单元组成,表示一片可操作空间
- 管理:人为的去操作一片空间的申请,使用和释放
- 内存管理:开发者主动申请空间,使用空间,释放空间
- 管理流程:申请-使用-释放
javascript中的垃圾回收
- javascript中内存管理是自动的
- 对象不再引用时是垃圾
- 对象不能从根上访问到时是垃圾
javascript中的可达对象
- 可以访问到的对象就是可达对象(引用,作用域链)
- 可达的标准就是从根触发是否能够被找到
- javascript中的根就可以理解为是全局变量对象
javascript中的引用和可达
obj假如是某个函数返回的
里面的o1和o2互相引用
通过作用域链引用可以找到的可达对象。
如果把对o1的相关引用去掉,他就无法被找到,就变成了一个不可达对象,垃圾。
GC算法介绍
GC的定义与作用
- GC就是垃圾回收机制的简写
- GC可以找到内存中的垃圾,并释放和回收空间
GC里的垃圾是什么
- 程序中不再需要使用的对象
- 程序中不能再访问到的对象(不可达对象)
GC算法是什么
GC是一种机制,垃圾回收器完成具体的工作
工作内容就是查找垃圾释放空间,回收空间
算法就是工作时查找和回收所遵循的规则
常见GC算法
- 引用计数
- 标记清除
- 标记整理
- 分代回收
引用计数算法实现原理
- 核心思想:设置引用数,判断当前引用数是否为0
- 引用计数器
- 引用关系改变时修改引用数字
- 引用数字为0时立即回收
引用计数算法优缺点
优点
- 发现垃圾时立即回收(实时监控引用数,为0立即回收)
- 最大程度减少程序卡顿(实时监控,实时回收释放内存,某种意义上保证内存不会被占满)
缺点
- 无法回收循环引用的对象(计数一直不为0)
- 时间开销大,资源消耗较大(多维护了计数器,时刻维护计数器,维护计数器的过程需要时间)
标记清除算法实现原理
- 核心思想:分标记和清除二个阶段
- 遍历所有对象标记活动对象
- 遍历所有对象清除没有标记对象
- 回收相应空间
例如
内存里有这些对象,GC工作时会递归找到可达对象,图中a-d,c-e,进行标记
然后遍历所有清除不可达对象,(a1和b1找不到属于不可达对象,未被标记),这个a1和b1引用计数就回收不了因为引用一直存在,循环引用。
标记清除的优缺点
优点:
- 最大优点可以清除循环引用(上例中就可以清除,因为是不可达对象,未被标记)
缺点:
- 不会立即回收垃圾对象,不像引用计数实时监控,第一次遍历对象发现不可达对象,也无法进行清除,只能是先标记,第二次再清除,回收。
- 会造成空间碎片化,如图:
不可达对象释放到空闲链表里的时候,因为有中间可达对象并没有被释放,所以他们是不连续的[域1,域2,...,域1],这样会造成空间碎片化,假设说每个域为1, 在我们需要2.5个内存时,这片空间就无法满足了。
标记整理算法实现原理
- 标记整理可以看作是标记清除的增强
- 标记阶段的操作和标记清除一致
- 清除阶段会先执行整理,移动对象位置
优点
- 减少碎片化空间
缺点
- 不会立即回收垃圾对象
- 移动对象位置,回收效率慢
认识V8
- v8是一款主流jsvascript执行引擎
- v8采用即时编译
- v8内存设限(64位不超1.5G,32位不超800M,因为1本身是针对浏览器的,现有大小对网页应用来说足够了,2,内部垃圾回收机制也决定了采用这设置是非常合理的,管方做过测试,当垃圾内存达到1.5G时,采用增量标记的算法回收,只需要消耗50ms,非增量标记回收1秒钟,从用户体验来说1秒已经算是很长了,所以就以1.5G为界限了(变大消耗时间会变长)。)
v8垃圾回收策略
- 采用分代回收的思想
- 内存分为新生代,老生代
- 针对不同对象采用不同的算法
v8常用GC算法
- 分代回收
- 空间复制
- 标记清除
- 标记整理
- 标记增量
v8如何回收新生代对象
- v8内存空间一分为二(新生代/老生代)
- 小空间用于储存新生代对象(62位32M/32位16M)
- 新生代指的是存活时间较短的对象
新生代对象回收实现
回收过程采用复制算法+标记整理
- 新生代内存区分为两个等大小空间
- 使用空间From,空闲空间为To
- 活动对象储存于From空间中
- 触发回收操作时,先标记整理From空间将活动对象和不可达对象都进行整理,把不可达对象整理成连续的(空间规整)),然后将活动对象复制拷贝至To。
- 然后From与To交换空间,完成释放(这回原来的From就成了To,原来的T哦就成了From)
回收细节说明:
- 拷贝过程中可能出现晋升
- 晋升就是将新生代对象移至老生代
- 一轮GC还存活的新生代需要晋升
- To空间的使用率超过25%晋升(防止交换空间后新生代内存不够使用)
老生代对象回收
老年代对象说明
- 老年代对象存放在右侧老生代区域
- 64位操作系统1.4G,32操作系统700M
- 老年代对象就是指存货时间较长的对象
老年代对象回收实现
- 主要采取标记清除,标记整理,增量标记算法
- 正常使用标记清除完成垃圾空间的回收(主要使用标记清除,虽然会有一些碎片,但是相对来说速度很明显提升)
- 采用标记整理进行空间优化(使用时机,新生代对象往老生代区域移动时且这个时间节点上,老生代区域空间又不足以来存放新生代移过来的对象,这时就会触发标记整理,把之前的碎片化空间整理来存放)
- 采用标记增量进行效率优化。
标记增量如图,会分段执行标记回收工作,让使用者尽量感受不到程序的暂停,即使时最后的清除阶段也不会让使用者感觉到卡顿。
细节对比
- 新生代区域垃圾回收使用空间换时间
- 老生代区域垃圾回收不适合复制算法(因为本来空间就比较大,里面存放的对象也多,划分俩空间的话首先是浪费,其次因为对象多,复制操作得不偿失。)
代码调试(性能检测)
Performance工具介绍
使用Performance时刻监控内存
内存问题的体现
- 页面出现延迟加载或经常性暂停
- 页面持续性出现糟糕的性能
- 页面性能随时间越长越来越差
界定内存问题的标准
- 内存泄漏:内存使用持续升高,不见释放
- 内存膨胀:在多数设备都存在性能问题。(指需要的内存,硬件设备无法提供)
- 频繁垃圾回收:通过内存变化图进行分析
监控内存的几种方式
浏览器任务管理器(shift+esc)右击调出jsvascript所占内存
第一个内存是dom节点内存,持续增长证明在创建节点,后面javascript内存是可达对象内存,持续增长证明我们在创建对象,如果一直增长不见下降,那就是有内存泄漏发生,如果频繁 飙高降低,证明在频繁GC
Timeline时序图记录
测试一下,我使用的是Edge,打开f12性能模块,谷歌里是Performance差异是中英文差异,然后点击录制
然后进行对界面操作,之后点击停止,会有你这一段时间操作过程的内存以及浏览器重绘的过程记录,在这里包括也能刷新看到界面一个加载过程,可以看到那里导致加载慢
频繁点击按钮,这里就不断创建大的字符串放到数组里,可以看到内存的升高,降低是因为浏览器的GC,如果时序图这里内存一直升高不释放,证明有内存泄漏了(可以找到对应时间节点查看),如果一直频繁飙高降低,这也是不正常的,证明在频繁的GC。(原始类型是放在栈上的,引用类型是放在堆上的)
什么是分离DOM
- 垃圾对象时的dom节点(从dom上脱离,js里也没有引用时的dom)
- 分离状态的dom(从dom树上脱离,但是js里有引用不释放,内存泄漏了)
堆快照查找分离dom
用代码演示下
这样界面没有,但是js引用有,这就是分离dom,我们看下如何查找这种dom。
找到Edge内存模块
先直接获取快照,方便对比 搜索detached 发现没有分离dom
然后点击按钮 再从用户配置获取新的快照,搜索deta,发现我们创建的分离dom
这里就可以看到分离的dom和一些它的相关信息,如果想要释放的话tepEle手动赋值null就可以了
判断是否存在频繁垃圾回收
- GC工作时程序是停止的,
- 频繁过长的gc会导致应用假死
- 用户使用感知卡顿
频繁GC判断
timeline中频繁上升下降
浏览器任务管理中数据频繁增大或减小
https://jsbench.me/
JSBench 用来测试代码执行速度
放入对比代码然后run 可以查看执行效率
也可以放入一些前置dom或者js代码。(测试所需)
经过一些测试有一些运行较快的总结如下:
避免全局变量,全局变量特点:
**挂载在window下
至少一个引用数
存活更久,但持续占用内存**
避免全局查找
全局查找相关:
- 目标变量不存在当前作用内,通过作用域链向上查找
- 减少全局查找降低时间消耗
- 减少不必要全局变量定义
- 全局变量数据局部化(必须要全局的话,可以缓存全局)
避免循环引用(全局引用指多个对象间存在互相引用)
采用字面量代替new操作
setTimeout替换setInterval
采用事件委托
合并循环变量和条件
在原型对象上新增实例对象需要方法
不为了闭包而闭包。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。