今天在看一些文章时,偶然看到了一篇有关于内存泄漏
的文章,当时就想起了一些自己做过的项目的一些往事。
以前做的客服系统,客服人员在使用的时候,有时会出现页面崩溃的情况,当时我们科室内部派了一些人员去驻场,协助排查问题,其中就有内存泄漏
的问题。但是的派驻人员没有我,我也没有专门排查过内存泄漏
,只是知道一些有关于内存泄漏
的知识和什么会导致内存泄漏
,算是知道的比较基础的。今天心血来潮,准备系统地学习和整理输出一篇有关于内存泄漏
的文章。
本篇文章是我看过一些相关文章后,按照了解、检测、定位处理和防治
的四个步骤来讲述内存泄漏
。
了解篇
学习内存泄漏
相关知识,你肯定要了解它的概念,以及什么会导致内存泄漏
。 本章也是按照这两个点进行讲述。
什么是内存泄漏
内存泄漏
是指程序中分配的内存空间在不再需要时未被正确释放或回收,导致该内存空间无法被再次使用,最终导致系统内存资源的浪费和程序性能下降的现象
。
内存泄漏
,就是由于没有被浏览器的垃圾回收机制给正常清理内存
。
在JavaScript中,内存是有生命周期的
,主要包括以下几个阶段:
分配内存
:当程序中创建变量、对象或其他数据结构时,JavaScript引擎会在内存中分配空间来存储这些数据。
使用内存
:在程序执行过程中,变量和对象会被使用和修改,占用内存空间。
释放内存
:当某个变量或对象不再被引用或需要时,JavaScript引擎会自动执行垃圾回收机制来释放这部分内存空间,以便再次使用。
垃圾回收
:JavaScript引擎会定期或在特定条件下执行垃圾回收,检查内存中不再被引用的对象和变量,并将其释放,以避免内存泄漏
和节省系统资源。
在JavaScript中,对象存储在堆内存中,可以通过引用链从根对象访问它们。垃圾收集器是 JavaScript 引擎的一个后台进程,负责识别无法访问的对象、删除它们并回收内存。
以下是垃圾收集器根据对象引用链的示例:
当在内存中应该在垃圾回收周期中清理的对象,却因为另一个对象的无意引用而保持可访问时,就会发生内存泄漏
。保留冗余对象在内存中会导致应用程序使用过多的内存,可能导致性能下降。
什么会导致内存泄漏
内存泄漏
可能由以下几个常见原因导致:
意外全局变量
:全局变量会一直存在于内存中,直到页面关闭或程序结束。如果过多的全局变量没有被及时释放,将会导致内存泄漏
。未及时清理定时器和事件监听器
:在JavaScript中,如果创建了定时器或事件监听器,但在不再需要它们时未手动清除,这些定时器和监听器会继续占用内存,导致内存泄漏
。闭包
:闭包是指函数内部定义的函数,它可以访问外部函数的变量。如果闭包中引用了外部函数的变量,并且这些变量不再需要时未被释放,就会导致内存泄漏
。缓存
:在缓存数据时,如果没有合理地管理缓存的大小和生命周期,可能会导致内存泄漏
。过多的缓存数据占用内存空间,而且如果没有及时清理过期的缓存数据,也会导致内存泄漏
。循环引用
:当两个或多个对象之间相互引用,并且这些对象之间的引用形成了一个闭环时,即使这些对象已经不再被程序所需要,垃圾回收机制也无法释放它们占用的内存空间,从而导致内存泄漏
。DOM操作
:频繁对DOM元素进行创建、删除、修改等操作,但未正确释放这些DOM元素所占用的内存空间,也会导致内存泄漏
。- console导致的
内存泄漏
因为打印后的对象需要支持在控制台上查看,所以传递给console.log方法的对象是不能被垃圾回收的。我们需要避免在生产环境用console打印对象。 - ES6 的一些语法的使用:Map、Set 等
具体示例可参考:www.yuque.com/cuggz/feplu...
检测篇
调试内存问题是一项复杂的任务,我们可以利用
Chrome DevTools
来查看内存图和识别潜在的内存泄漏
。 现代浏览器都提供了强大的开发者工具,其中包含了内存分析工具,可以帮助开发人员监控内存使用情况。通过浏览器的内存分析工具,可以查看内存占用情况、对象分配情况、堆快照等信息,从而帮助识别潜在的内存泄漏
问题。
在检测时,尽量选择在无痕浏览器中检测,清除浏览器插件和缓存的影响。
使用性能分析器可视化内存消耗
定位内存泄漏
的方法:(通过浏览器的性能面板)
- 打开开发者工具,选择 Performance(性能) 面板,在顶部的 capture 字段勾选 Memory(内存),或直接选择 Memory 面板
- 点击左上角的录制按钮
- 在页面上进行各种操作,模拟用户的使用情况,也可以称为"问题复现"
- 一段时间后,点击对话框的 stop 按钮,面板上会显示这段时间的内存占用情况
- 如果内存占用基本平稳,则说明不存在
内存泄漏
;相反,如果内存占用不断增加,则可能存在内存泄漏
。
选中JS Heap(JS 堆),在下方显示的蓝线代表了这段记录期间JS堆内存信息的变化情况。
有一些大佬表示,根据这条蓝线可以初步判断是否存在内存泄漏
:如果这条蓝线持续上升,那很可能存在内存泄漏
。 然而,实际上这种说法有些偏颇。JS堆内存占用率的增长并不一定意味着内存泄漏
,只能表明有许多未释放的内存存在。要确定这些内存是否真正被使用,或者确实存在内存泄漏
,还需要进一步的排查和分析。
Memory(内存):内存快照对比
开发者工具的 Memory (内存)选项,可以更精确地定位内存使用情况。
以下是一些步骤,可以帮助开发人员通过对比内存快照来识别内存泄漏
:
- 打开浏览器开发者工具并切换到内存分析工具("Memory")。
- 在内存分析工具中,可以手动触发一次内存快照,记录当前时刻的内存使用情况。
- 进行一些操作或者触发一些事件,使得内存中可能存在的
内存泄漏
问题被激活。 - 返回到原页面,再次触发内存快照,记录第二次内存使用情况。
- 对比两次内存快照,查看内存中的对象和数据是否有明显的增加或者未释放的情况。特别要注意查看是否有多余的对象被引用、未释放的事件监听器、定时器等情况。
- 根据对比结果,定位可能存在
内存泄漏
的代码段,并进行优化和改进。
通过对比内存快照的方式,开发人员可以更直观地了解内存使用情况,并及时发现潜在的内存泄漏
问题,从而提高应用的性能和稳定性。
定位处理篇
从上一章节,我们可以检测出是否存在内存泄漏
。
本章节主要讲定位内存泄漏
,并进行处理。最重要的还是定位,定位到了,处理相对而言就很简单了。 我们可以通过内存快照的对比结果,来定位出导致内存泄漏
的文件地址。
切换到Comparison(性能)视图,通过比较多个快照之间的差异来找出内存泄露的对象。
上图对比结果的列表中,需要特别注意这个 #Delta ,如果是正值,就代表新生成的内存多,释放的内存少。其中的闭包项,如果是正值,就说明存在内存泄漏
。 我们可以根据#Delta(增量)进行排序,进而确定导致内存泄漏
的文件地址。 下面我们到代码里找一个内存泄漏
的问题: 这里的内容还是比较多的,我们可以重点关注几个:
- closure(闭包)
防治篇
内存泄漏
是一个常见的问题,特别是在前端开发中。以下是一些常见的方法和技巧,可以帮助开发人员预防和解决内存泄漏
问题:
ESLint
是一个用于检测 JavaScript 代码中潜在问题
的静态代码分析工具,可以帮助开发人员在编码阶段发现潜在的内存泄漏
问题。及时释放不再使用的对象和资源
:确保在不需要使用的对象或者资源时及时释放它们,避免长时间占用内存空间。避免循环引用
:当两个对象相互引用时,如果没有正确的释放引用,就会导致内存泄漏
。尽量避免循环引用,或者在不需要时手动断开引用。合理使用事件监听器和定时器
:当不再需要使用的事件监听器和定时器时,要确保及时移除它们,以防止内存泄漏
。使用合适的数据结构和算法
:在编写代码时,选择合适的数据结构和算法是防止内存泄漏
的重要因素。避免使用过多的全局变量和嵌套循环等可能导致内存泄漏
的操作。使用浏览器开发者工具进行内存分析
:定期使用浏览器开发者工具中的内存分析工具,查看内存使用情况,并及时发现潜在的内存泄漏
问题。- 使用工具和库进行
内存泄漏
检测:有一些工具和库可以帮助开发人员检测和定位内存泄漏
问题,如Chrome DevTools、Memory Leak Detection等。 进行代码审查和测试
:定期进行代码审查和测试,发现潜在的内存泄漏
问题,并及时修复。
通过以上方法和技巧,开发人员可以有效预防和解决内存泄漏
问题,提高应用的性能和稳定性。
更详细的方案可参考:juejin.cn/post/727201...