内存相关问题
内存泄漏:当不再用到的对象内存,没有及时被回收,想要避免就让无用数据不存在引用关系。 内存膨胀:即在短时间内内存占用极速上升到达一个峰值,想要避免需要使用技术手段减少对内存的占用。 频繁GC: GC执行的特别频繁,一般出现在频繁使用大的临时变量导致新生代空间被装满的速度极快,而每次新生代装满时就会触发 GC,频繁 GC 同样会导致页面卡顿,想要避免的话就不要搞太多的临时变量,因为临时变量不用了就会被回收。
内存泄露场景
闭包
隐式全局变量
函数中的局部变量在函数执行结束后这些变量已经不再被需要,所以垃圾回收器会识别并释放它们。但是对于全局变量,垃圾回收器很难判断这些变量什么时候才不被需要,所以全局变量通常不会被回收
function fn ( ) {
test1 = new Array ( 1000 ) . fill ( 'isboyjc1' )
this . test2 = new Array ( 1000 ) . fill ( 'isboyjc2' )
}
fn ( )
因为没有声明和函数中this的问题造成了两个额外的隐式全局变量,这两个变量不会被回收
DOM引用
< div id= "root" >
< ul id= "ul" >
< li> < / li>
< li> < / li>
< li id= "li3" > < / li>
< li> < / li>
< / ul>
< / div>
< script>
let root = document. querySelector ( '#root' )
let ul = document. querySelector ( '#ul' )
let li3 = document. querySelector ( '#li3' )
root. removeChild ( ul)
ul = null
li3 = null
< / script>
定时器
当不需要 interval 或者 timeout 时,最好调用 clearInterval 或者 clearTimeout来清除,另外,浏览器中的 requestAnimationFrame 也存在这个问题,我们需要在不需要的时候用 cancelAnimationFrame API 来取消使用
事件监听
< template>
< div> < / div>
< / template>
< script>
export default {
created ( ) {
window. addEventListener ( "resize" , this . doSomething)
} ,
beforeDestroy ( ) {
window. removeEventListener ( "resize" , this . doSomething)
} ,
methods : {
doSomething ( ) {
}
}
}
< / script>
事件的监听发布
< template>
< div> < / div>
< / template>
< script>
export default {
created ( ) {
eventBus. on ( "test" , this . doSomething)
} ,
beforeDestroy ( ) {
eventBus. off ( "test" , this . doSomething)
} ,
methods : {
doSomething ( ) {
}
}
}
< / script>
当实现了监听者模式并收集了相关的事件处理函数,其中引用的变量或者函数都被认为是需要的而不会进行回收
Map、Set强引用
let obj = { id : 1 }
let user = { info : obj}
let set = new Set ( [ obj] )
let map = new Map ( [ [ obj, 'hahaha' ] ] )
obj = null
console. log ( user. info)
console. log ( set)
console. log ( map)
map. delete ( obj) ;
obj = null ;
let weakSet = new WeakSet ( [ obj] )
let weakMap = new WeakMap ( [ [ obj, 'hahaha' ] ] )
obj = null
未清理的console
之所以在控制台能看到数据输出,是因为浏览器保存了我们输出对象的信息数据引用,也正是因此未清理的 console 如果输出了对象也会造成内存泄漏
chrome 内存泄漏定位步骤
先点击6手动进行一次垃圾回收,4、5都点上,然后点击1开始录制 可以看到快照对应下的内存曲线,如果在手动执行垃圾回收后,曲线并没有明显下降,说明可能存在内存泄漏 点击Memory,可以为我们提供更多详细信息,比如记录 JS CPU 执行时间细节、显示 JS 对象和相关的DOM节点的内存消耗、记录内存的分配细节等 其中的 Heap Profiling 可以记录当前的堆内存 heap 的快照,并生成对象的描述文件,该描述文件给出了当下 JS 运行所用的所有对象,以及这些对象所占用的内存大小、引用的层级关系等等,用它就可以定位出引起问题的具体原因以及位置。 先手动点击垃圾回收,然后操作页面,然后点击1生成快照,循环操作几次
Summary:按照构造函数进行分组,捕获对象和其使用内存的情况,可理解为一个内存摘要,用于跟踪定位DOM 节点的内存泄漏
Comparison:对比某个操作前后的内存快照区别,分析操作前后内存释放情况等,便于确认内存是否存在泄漏及造成原因
Containment:探测堆的具体内容,提供一个视图来查看对象结构,有助分析对象引用情况,可分析闭包及更深层次的对象分析
Statistics:统计视图
Constructor:显示所有的构造函数,点击每一个构造函数可以查看由该构造函数创建的所有对象
Distance:显示通过最短的节点路径到根节点的距离,引用层级
Shallow Size:显示对象所占内存,不包含内部引用的其他对象所占的内存
Retained Size:显示对象所占的总内存,包含内部引用的其他对象所占的内存
system、system/ Context 表示引擎自己创建的以及上下文创建的一些引用,这些不用太关注,不重要
closure 表示一些函数闭包中的对象引用
array、string、number、regexp 这一系列也能看出,就是引用了数组、字符串、数字或正则表达式的对象类型
HTMLDivElement、HTMLAnchorElement、DocumentFragment等等这些其实就是你的代码中对元素的引用或者指定的 DOM 对象引用
选择Comparison比较,或者右侧的filter
New:新建了多少个对象
Deleted:回收了多少个对象
Delta:新建的对象数 减去 回收的对象数
需要重点关注 Delta ,只要它是正数就可能存在问题
closure 上面也说过代表闭包引用,可以查看到代码中发生引用的位置 根据其他的如Array等构造函数的Distance层级也能定位发生泄漏的位置 如上就能定位两个问题,一是代码 30 行的闭包引用数组造成的内存泄漏,二是数组的元素不断增多造成的内存泄漏。
内存泄漏检测
FinalizationRegistry
给对象注册一个在被垃圾回收时触发的回调,排除掉被回收调的对象
详情
第三方工具
node-heapdump:heapdump是一个dumpV8堆信息的工具 node-profiler:node-profiler 是 alinode 团队出品的一个 与node-heapdump 类似的抓取内存堆快照的工具 Easy-Monitor:轻量级的 Node.js 项目内核性能监控 + 分析工具 Node.js-Troubleshooting-Guide:Node.js 应用线上/线下故障、压测问题和性能调优指南手册 alinode:Node.js 性能平台(Node.js Performance Platform)是面向中大型 Node.js 应用提供 性能监控、安全提醒、故障排查、性能优化等服务的整体性解决方案。