js内存泄漏及排查详解

news/2024/7/7 22:26:24

js内存泄漏及排查详解

常见内存泄漏及解决方案

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

隐式全局变量

在局部作用域中,等函数执行完毕,变量就没有存在的必要了,浏览器的垃圾回收机制很快进行回收,但是对于全局变量,很难判断什么时候不用这些变量,无法正常回收;所以,尽量少使用全局变量。

function foo() {
  a = 'test'
}
// 上面的写法等价于
function foo() {
  window.a = 'test'
}

上面的a变量应该是foo()内部作用域变量的引用,由于没有使用关键词(letconstvar)来声明这个变量,这时变量a就被创建成了全局变量,这时候就会导致内存泄漏。

解决方式:使用 varletconst 来定义变量。或者在js文件开头添加 'use strict',开启严格模式。

function bar() {
  this.a = 'test'
  // 函数自身发生调用,this指向全局对象window
}
bar();

闭包

闭包是代码块和创建该代码块的上下文中数据的结合(函数套函数,子函数引用了父函数的参数或变,并且被外部引用,形成不被释放的作用域)。

在使用闭包的时候,就会造成严重的内存泄漏,因为闭包中的局部变量,会一直保存在内存中。

function fn(){
  let result = {}
  return function(){
    // 因为闭包内引用了result,导致它不会被垃圾机制回收,导致内存泄漏
    return result;
  }
}
let fn1 = fn()
fn1()

上面的代码可以直接通过将fn1置为null来清除引用。也可以少用闭包的方式。

未清除的DOM引用

<div id="app">
  <ul id="ul">
    <li></li>
    <li></li>
    <li id="li3"></li>
    <li></li>
  </ul>
</div>
<script>
  let app = document.querySelector('#app')
  let ul = document.querySelector('#ul')
  let li3 = document.querySelector('#li3');
  app.removeChild(ul);
</script>

上面的代码虽然调用removeChildulDOM上移除了,但是由于ul变量中仍存在引用,整个ul及子元素都不能被垃圾回收机制清除。

因此需要手动将引用清除:

ul = null;

但是此时li3变量还引用着ul的子节点,ul还是不能够垃圾回收机制清除,还需要手动将li3解除引用。

li3 = null;

定时器

setInterval或者setTimeout在不需要使用的时候,没有被clear,导致定时器的回调函数及其内部依赖的变量都不能被回收,这也会造成内存泄漏。另外,浏览器中的 requestAnimationFrame 也存在这个问题,在不需要的时候用 cancelAnimationFrame 来取消使用。

const data = {};
setInterval(() => {
  console.log(data);
}, 1000)

循环引用

循环引用 在引用计数策略下会导致内存泄漏,标记清除不会。

function fn() {
  const a = {};
  const b = {};
  a.b = b;
  b.a = a;
} 
fn();

ab的引用次数都是2,fn()执行完毕后,两个对象都已经离开环境。
在标记清除方式下是没有问题的,但是在引用计数策略下,ab的引用次数不为0,不会被垃圾回收器回收内存。如果fn函数被大量调用,就会造成内存泄漏,这时候就需要手动解除引用(置为null)。

未清理的console

如果在console中输出了对象,那么浏览器就需要把这个引用关系保存下来,才能在控制台上看到相应的对象,这样同样也会造成内存泄漏。

使用chrome devtool工具排查内存泄漏问题

查看内存曲线

在浏览器中打开开发者工具(通常都是F12快捷键打开)。首先可以查看Performance栏。

在这里插入图片描述
勾选memory,点击左上角的原点开始录制一段时间,如果出现内存曲线没有明显下降,说明可能存在内存泄漏。

查看内存情况

performance栏中如果看到曲线没有明显下降, 那么这时候就可以点击memory栏去查看更多的信息。

在这里插入图片描述
同样开始点击左上角的原点开始记录,通常需要录制多几遍(每次录制前都先点击垃圾回收按钮先回收掉可以收集的垃圾),然后进行对比。

在这里插入图片描述
这里可以很明显看到内存占用一次比一次高,选择快照对比,进行内存泄漏的排查。

在这里插入图片描述
首先可以先查看shallow size(对象本身占用内存的大小,不包含其引用的对象),retained size(对象本身的Shallow Size + 对象能直接或间接访问到的对象的Shallow Size),如果retained size远大于shallow size,说明就是这里有泄漏。

在这里插入图片描述
在这个记录中,示例代码是通过新增了19个隐式全局变量且每个变量的值new Array(100000)都是导致的内存泄漏。

而对于未清除的DOM引用,我们可以查看快照中有没有Detached XXXXElement对象。

在这里插入图片描述

建议

文章中使用的demo都是简单的,对于项目中想排查问题来说多了非常多变量,想要定位问题比较困难,这里个人列举几个比较有用的建议:

  1. 尽量使用没有混淆的代码:

打包后的代码往往经过了混淆和压缩,在生产环境上这是必要的,但在debug时却会成为我们的绊脚石,不便于阅读。

  1. 排查问题时使用production模式编译出来的代码:

dev模式下往往会开启一些方便开发的特性,例如热更新等。但它们可能会占用一部分的内存,影响到内存问题的排查,所以建议还是使用production模式编译出来的代码进行问题排查。

  1. 屏蔽所有浏览器插件:

屏蔽浏览器插件最快的方式就是打开无痕窗口。浏览器插件给我们带来很多便利,但插件注入的额外逻辑有时也会影响内存问题的排查。例如vue-devtools会记录下每一个vuex mutaions,导致内存无法释放。

  1. 在现场打内存快照,便于跳转到源代码所在行:

尽管devTools记录下来的内存快照文件可以单独加载展示,但还是建议在记录下内存快照的时候“趁热”分析,因为这时还能从retaining tree上跳转到代码所在行,有时候对定位问题也很有帮助。


http://lihuaxi.xjx100.cn/news/1289239.html

相关文章

v8-tc39-ecma262:concat,不只是合并数组

如上图&#xff0c;解释如下&#xff1a; 如果是对象o&#xff0c;转换为对象新建数组A设n0,用于最后赋值给A&#xff0c;确保A的长度正确预先把值设置到items(这里不知何意&#xff1f;)循环items&#xff0c;设置元素为E E是否可展开如果可展开 有len下标&#xff0c;则获取…

QT多线程之QtConcurrent::run()

QtConcurren导读 QtConcurrent提供了编写多线程程序的高级api&#xff0c;也即不使用低级线程原语&#xff0c;而其他实现多线程的方式&#xff0c;例如子类化QThread、QObject::moveToThread()、子类化QRunnable对于共享数据的保护都要使用低级线程原语&#xff0c;这无疑是要…

(0day通用)中庆纳博某系统敏感信息泄露+未授权修改密码

申明&#xff1a;本次测试只作为学习用处&#xff0c;请勿未授权进行渗透测试&#xff0c;切勿用于其它用途&#xff01; 1.漏洞背景 北京中庆纳博信息技术有限公司&#xff0c;简称中庆纳博&#xff0c;是有20年历史的中庆集团旗下核心企业&#xff0c;专注于教育信息化的深度…

通过shell的while read line对一个文件中的考试分数进行人员分组

通过shell的while read line对一个文件中的考试分数进行人员分组&#xff0c;并记录分数 [rootecs-18b3 ~]# cat split.sh #!/bin/bash while read line donameecho ${line}|awk {print $1}scoreecho ${line}|awk {print $2}if [ ${score} -gt 90 ];thenecho -e "${name…

linux:Docker 退出容器但不关闭当前容器

参考&#xff1a; Docker 退出容器但不关闭当前容器_docker 怎么把容器hold_leoe_的博客-CSDN博客

前后端交互时数据加密的目的是什么

对于新手在网页开发的时候&#xff0c;总会遇到对数据加密的误解&#xff0c;尤其是刚开始接触相关代码看了一些用例之后90%的人免不了会想过一个问题&#xff0c;就是前后端加解密是为了什么。 例如下面的代码 后端加密代码 import javax.crypto.Cipher; import javax.cryp…

代码随想录算法训练营第2天| 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II

今日学习的文章链接&#xff0c;或者视频链接 第一章 数组part02 第一章 数组part02 自己看到题目的第一想法 看完代码随想录之后的想法 977: class Solution { public:vector<int> sortedSquares(vector<int>& nums) {int left 0;int right nums.size…

chatgpt赋能python:Python迭代运算:概述、应用及效果分析

Python迭代运算&#xff1a;概述、应用及效果分析 在Python编程领域中&#xff0c;迭代运算是一项基础性操作。它不仅适用于循环遍历数据&#xff0c;还支持函数式编程中的高阶函数应用&#xff08;例如map、filter等&#xff09;。本文将从多个方面探讨Python迭代运算的应用和…