什么?!你居然连个内存泄漏都排查不出来

news2024/10/24 8:25:57

公众号:程序员白特,欢迎一起交流学习~

在日常的业务开发中,偶尔会出现内存泄漏的情况,那么我们该怎么排查呢?现在跟着文章一起学习下吧~

使用Chrome devTools查看内存情况

打开Chrome的无痕模式,以屏蔽Chrome插件对我们之后测试内存占用情况的影响。然后打开开发者工具,找到Performance栏,可以看到一些功能按钮,如开始录制按钮、刷新页面按钮、清空记录按钮、记录并可视化js内存、节点、事件监听器按钮、触发垃圾回收机制按钮等。

请简单录制一下百度页面,观察我们能够获取到什么信息,如下动图所示:

从图表中我们可以清楚地观察到,在页面加载过程中JS Heap(js堆内存)、documents(文档)、Nodes(DOM节点)、Listeners(监听器)、GPU memoryGPU内存)的最低值、最高值以及随时间的变化趋势,这是我们关注的重点。

查看开发者工具中的Memory一栏,主要用于记录页面堆内存的具体情况以及js堆内存随加载时间线动态的分配情况。

堆快照类似于照相机,可以记录当前页面的堆内存情况。每次进行快照,都会生成一条快照记录。

根据上图所示,我们首先进行了一次快照,记录了当时堆内存空间占用为33.7MB。随后,我们点击了页面中的一些按钮,再次执行了一次快照,记录了当时堆内存空间占用为32.5MB。此外,通过点击相应的快照记录,我们可以查看当时所有内存中的变量情况,包括结构和占总内存的百分比等信息。

在记录数据后,我们可以观察到图表右上角有起伏的蓝色和灰色柱状图,其中蓝色代表当前时间线下所占用的内存;灰色表示之前占用的内存空间已被清除释放。

在发现存在内存泄漏的情况时,我们可以使用Memory来更清晰地确认问题并定位问题。

首先,可以使用Allocation instrumentation on timeline来确认问题,如下图所示:.

内存泄漏的场景

  • 闭包使用不当引起内存泄漏
  • 全局变量
  • 分离的DOM节点
  • 控制台的打印
  • 遗忘的定时器

1. 闭包使用不当引起内存泄漏

使用PerformanceMemory来查看一下闭包导致的内存泄漏问题

<button onclick="myClick()">执行fn1函数</button>
<script>
 function fn1 () {
   let a = new Array(10000)  // 这里设置了一个很大的数组对象
   let b = 3
   function fn2() {
     let c = [1, 2, 3]
   }
   fn2()
     return a
   }
   let res = [] 
   function myClick() {
     res.push(fn1())
   }
 }
</script>

fn1函数执行上下文退出后,本应将该上下文中的变量a视为垃圾数据并进行回收。然而,由于fn1函数最终将变量a返回并赋值给全局变量res,这导致对变量a的引用产生,使得变量a被标记为活动变量并一直占用相应的内存。如果假设后续不再使用变量res,那么这就是一个闭包使用不当的例子。

为了能够在performance的曲线图中观察效果,我们设置了一个按钮,每次点击执行时,将fn1函数的返回值添加到全局数组变量res中。如下图所示:

  • 在每次录制开始时手动触发一次垃圾回收机制,这是为了确认一个初始的堆内存基准线,便于后面的对比。然后我们点击了几次按钮,即往全局数组变量res中添加了几个比较大的数组对象。最后再触发一次垃圾回收,发现录制结果的JS Heap曲线刚开始成阶梯式上升的,最后的曲线的高度比基准线要高,说明可能是存在内存泄漏的问题。

  • 在得知有内存泄漏的情况存在时,我们可以改用Memory来更明确地确认问题和定位问题。

  • 首先可以使用Allocation instrumentation on timeline来确认问题,如下图所示:.

  • 每次点击按钮后,动态内存分配情况图上都会出现一个蓝色的柱形,而且在我们触发垃圾回收后,蓝色柱形都没有变成灰色柱形,也就是说之前分配的内存没有被清除。

  • 因此,我们可以明确地确认存在内存泄漏的问题。接下来,我们需要精确定位问题,可以使用Heap snapshot来进行定位,如下图所示:

  • 首先,我们需要点击快照记录初始的内存情况,然后多次点击按钮后再次点击快照,记录此时的内存情况。我们发现,从原来的1.1M内存空间变成了1.4M内存空间。接着,我们选中第二条快照记录,可以看到右上角有一个All objects的字段,表示展示当前选中的快照记录所有对象的分配情况。我们想要知道的是第二条快照与第一条快照的区别在哪,因此选择Object allocated between Snapshot1 and Snapshot2,即展示第一条快照和第二条快照存在差异的内存对象分配情况。这时,我们可以看到Array的百分比很高,初步可以判断是该变量存在问题。点击查看详情后,就能查看到该变量对应的具体数据了。

这是一个判断闭包是否导致内存泄漏问题并简单定位的方法

2. 全局变量

全局变量通常不会被垃圾回收,但并非所有变量都不能存在于全局范围。有时由于疏忽,会导致某些变量流失到全局,比如未声明变量,却直接对其赋值,这将导致该变量在全局范围创建。如下所示:

function fn1() {
   // 此处变量name未被声明
   name = new Array(99999999)
}
fn1()
  • 此时,当出现这种情况时,会自动在全局范围内创建一个变量name,并将一个大型数组赋值给name。由于它是全局变量,所以该内存空间将一直保持不释放。

  • 要解决这个问题,我们需要自己在平时多加注意,不要在变量声明之前进行赋值。另外,也可以考虑开启严格模式,这样在不知不觉中犯错时,会收到错误警告。例如,

function fn1() {
  'use strict';
  name = new Array(99999999)
}
fn1()

3. 分离的DOM节点

如果您手动删除了一个dom节点,本应该释放该节点占用的内存,但由于疏忽导致某处代码仍然引用了该被移除节点,最终导致该节点占用的内存无法被释放,这种情况是很常见的。

<div id="root">
 <div class="child">我是子元素</div>
 <button>移除</button>
</div>
<script>
 let btn = document.querySelector('button')
 let child = document.querySelector('.child')
 let root = document.querySelector('#root')
  
 btn.addEventListener('click', function() {
   root.removeChild(child)
 })
</script>

代码的功能是在点击按钮后移除.child节点,尽管节点在点击后确实从dom中移除了,但全局变量child仍然保留对该节点的引用,导致该节点的内存无法释放,建议使用Memory的快照功能进行检测,具体操作如下图所示。

先记录下初始状态的快照,然后在点击移除按钮后,再次点击一次快照。此时,我们无法看出内存大小的任何变化,因为被移除的节点占用的内存非常小,可以忽略不计。但是,我们可以点击第二条快照记录,在筛选框中输入“detached”,这样就会显示所有脱离但尚未被清除的节点对象。

解决办法如下图所示:

<div id="root">
 <div class="child">我是子元素</div>
 <button>移除</button>
</div>
<script>
 let btn = document.querySelector('button')
 btn.addEventListener('click', function() { 
   let child = document.querySelector('.child')
   let root = document.querySelector('#root')
   root.removeChild(child)
 })
</script>

修改非常简单,只需将对.child节点的引用移动到click事件的回调函数中。这样,当移除节点并退出回调函数的执行上下文后,对该节点的引用将自动清除,从而避免了内存泄漏的情况。让我们来验证一下。如下图所示:

结果很明显,这样处理过后就不存在内存泄漏的情况了

4. 控制台的打印

<button>按钮</button>
<script>
 document.querySelector('button').addEventListener('click', function() {
   let obj = new Array(1000000)
   console.log(obj);
 })
</script>

我们在按钮的点击回调事件中创建了一个很大的数组对象并打印,用performance来验证一下

开始录制时,首先进行一次垃圾回收以清除初始内存。然后点击按钮三次,即执行了三次点击事件。最后再次触发一次垃圾回收。观察录制结果发现,JS Heap曲线呈阶梯状上升,并且最终保持的高度比初始基准线高很多。这说明每次执行点击事件时,创建的大型数组对象obj由于被浏览器保存并且无法回收,导致内存占用增加。

接下来注释掉console.log,再来看一下结果:

<button>按钮</button>
<script>
 document.querySelector('button').addEventListener('click', function() {
   let obj = new Array(1000000)
   // console.log(obj);
 })
</script>

可以看到没有打印以后,每次创建的obj都立马被销毁了,并且最终触发垃圾回收机制后跟初始的基准线同样高,说明已经不存在内存泄漏的现象了

其实同理 console.log也可以用Memory来进一步验证

未注释 console.log

注释掉了console.log

最后简单总结一下:在开发环境下,可以使用控制台打印便于调试,但是在生产环境下,尽可能得不要在控制台打印数据。所以我们经常会在代码中看到类似如下的操作:

// 如果在开发环境下,打印变量obj
if(isDev) {
   console.log(obj)
}

这样就避免了生产环境下无用的变量打印占用一定的内存空间,同样的除了console.log之外,console.errorconsole.infoconsole.dir等等都不要在生产环境下使用

5. 遗忘的定时器

定时器也是平时很多人会忽略的一个问题,比如定义了定时器后就再也不去考虑清除定时器了,这样其实也会造成一定的内存泄漏。来看一个代码示例:

<button>开启定时器</button>
<script>
 function fn1() {
   let largeObj = new Array(100000)
   setInterval(() => {
     let myObj = largeObj
   }, 1000)
 }
 document.querySelector('button').addEventListener('click', function() {
   fn1()
 })
</script>

这段代码是在点击按钮后执行fn1函数,fn1函数内创建了一个很大的数组对象largeObj,同时创建了一个setInterval定时器,定时器的回调函数只是简单的引用了一下变量largeObj,我们来看看其整体的内存分配情况吧:

按道理来说点击按钮执行fn1函数后会退出该函数的执行上下文,紧跟着函数体内的局部变量应该被清除,但图中performance的录制结果显示似乎是存在内存泄漏问题的,即最终曲线高度比基准线高度要高,那么再用Memory来确认一次:

  • 在我们点击按钮后,从动态内存分配的图上可以看到一个蓝色柱形,表示浏览器为变量largeObj分配了一段内存。然而,这段内存并没有被释放,这说明存在内存泄漏的问题。其实,问题的原因是setInterval的回调函数内对变量largeObj有一个引用关系,而定时器一直未被清除,所以变量largeObj的内存也自然不会被释放。

  • 那么,我们如何解决这个问题呢?假设我们只需要让定时器执行三次,我们可以对代码进行一些修改:

<button>开启定时器</button>
<script>
 function fn1() {
   let largeObj = new Array(100000)
   let index = 0
   let timer = setInterval(() => {
     if(index === 3) clearInterval(timer);
     let myObj = largeObj
     index ++
   }, 1000)
 }
 document.querySelector('button').addEventListener('click', function() {
   fn1()
 })
</script>

现在我们再通过performancememory来看看还不会存在内存泄漏的问题

  • performance

这次的录制结果表明,最终曲线的高度与初始基准线的高度相同,这意味着没有发生内存泄漏。

  • memory

这里需要澄清一下,图中最初出现的蓝色柱形是因为我在录制后刷新了页面,可以忽略;接着我们点击了按钮,看到又出现了一个蓝色柱形,这时是为fn1函数中的变量largeObj分配了内存,3s后该内存被释放,变成了灰色柱形。因此可以得出结论,这段代码没有内存泄漏问题。

简要总结一下:在使用定时器时,务必在不需要定时器时清除,否则可能出现类似本例的情况。除了setTimeoutsetInterval,浏览器还提供了API,如requestAnimationFrame,也可能存在这种问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1497977.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java引用传递及基本应用

在 Java 中&#xff0c;传递参数的方式主要有两种&#xff1a;值传递&#xff08;传递的是对象的引用值&#xff09;和引用传递。本教程将重点介绍 Java 中的引用传递以及其基本应用。 1. 引用传递概念 在 Java 中&#xff0c;所有的方法参数都是通过值传递的。对于对象类型的…

市场低估了什么?

伍戈认为&#xff0c;市场低估了CPI和PPI的下行压力和政策的定力&#xff0c;一季度实际经济增速或与年度预期目标有些偏离&#xff0c;预计二季度开始逆周期政策逐步加力&#xff0c;从而引致名义GDP的阶段性趋稳过程。 核心观点&#xff1a; 1.时光若倒流&#xff0c;能否预见…

JS使用方式

JS是解释性语言&#xff0c;所以不需要搭建类似C#/Java之类的开发运行环境&#xff0c;因为他们是编译型语言。JS一般运行在浏览器中或者node环境中&#xff0c;这里都是JS引擎的功劳。 node环境使用 推荐使用nvm管理node版本&#xff0c;nrm管理代理地址。 安装node&#xf…

关于Vue3的一些操作

1. 设置浏览器自动打开 在package.json 中设置 dev: vite --open 2.给src文件夹配置别名 在vite.config.ts配置文件中添加以下内容 3. 如果2中有红色波浪线的问题 ***安装一个文件包***npm install types/node3. 在tsconfig.json配置文件中&#xff0c;找到配置项compi…

迷你内裤洗衣机排名前十名:推荐十款2024专业性高的内衣洗衣机

最近一段时间&#xff0c;关于内衣到底是机洗好&#xff0c;还是手洗好这个话题&#xff0c;有很多人都在讨论&#xff0c;坚决的手洗党觉得应该用手来清洗&#xff0c;机洗与其它衣物混合使用&#xff0c;会产生交叉感染&#xff0c;而且随着使用时间的推移&#xff0c;会变得…

【Maven】Maven 基础教程(五): jar 包冲突问题

《Maven 基础教程》系列&#xff0c;包含以下 5 篇文章&#xff1a; Maven 基础教程&#xff08;一&#xff09;&#xff1a;基础介绍、开发环境配置Maven 基础教程&#xff08;二&#xff09;&#xff1a;Maven 的使用Maven 基础教程&#xff08;三&#xff09;&#xff1a;b…

2575. 找出字符串的可整除数组(Go语言)

https://leetcode.cn/problems/find-the-divisibility-array-of-a-string/ 在看题解之前&#xff0c;我的代码是以下这样&#xff1a; package mainimport ("fmt" )func main() {fmt.Println(divisibilityArray("998244353", 3)) }func divisibilityArray…

数据备份:守护你的数字资产,安全无忧!

一、数据备份&#xff1a;数字时代的“保险箱” 在数字化日益盛行的今天&#xff0c;我们的工作、学习和生活都离不开各种电子设备。无论是电脑中的文档、图片&#xff0c;还是手机里的联系人、短信&#xff0c;都承载着我们的重要信息和回忆。然而&#xff0c;电子设备并非永…

基于C/S架构的在线阅读器

项目简介 本项目实现了用户的基本阅读功能。项目内容涉及到IO&#xff0c;网络编程&#xff0c;C&#xff0c;QT等知识点。本次项目服务器搭建在ubuntu上&#xff0c;客户端ui在QT中实现&#xff0c;客户端和服务器使用套接字通信。 一、基本功能展示 &#xff08;1&#xff…

关于制作一个Python小游戏(三)

目录 前言: 在前面我们已经了解过了关于制作pygame的使用和在里面游戏中的简单操作的内容了,今天我们主要讲的就是关于敌机的出现和如何去操控游戏中英雄飞机和敌机的出现 1.敌机的设计: 1.1敌机出场的实现: 1.1.1游戏启动后,每个一秒钟出现一架敌方飞机 1.1.2每架敌机向屏…

ETL与抖音数据同步,让数据流动无阻

在当今数字化时代&#xff0c;数据的价值日益凸显&#xff0c;企业需要从各种渠道获取有关用户行为、市场趋势和竞争对手活动的数据。作为一家专注于数据集成和转换的领先平台&#xff0c;ETLCloud为企业提供了强大的数据同步和转换功能。而与此同时&#xff0c;抖音作为一款热…

vcomp140.dll丢失如何修复,5种修复方法轻松搞定vcomp140.dll问题

vcomp140.dll文件的丢失可能会引发一系列系统运行与软件功能上的问题。具体来说&#xff0c;这个动态链接库文件是Visual C Redistributable的一部分&#xff0c;对于许多基于此环境开发的应用程序至关重要。一旦缺失&#xff0c;可能会导致部分应用程序无法正常启动或运行&…

深入浅出(二)MVVM

MVVM 1. 简介2. 示例 1. 简介 2. 示例 示例下载地址&#xff1a;https://download.csdn.net/download/qq_43572400/88925141 创建C# WPF应用(.NET Framework)工程&#xff0c;WpfApp1 添加程序集 GalaSoft.MvvmLight 创建ViewModel文件夹&#xff0c;并创建MainWindowV…

SAP Parallel Accounting(平行分类账业务)配置及操作手册(超详细的说明和测试)

SAP Parallel Accounting(平行分类账业务)配置及操作手册 1、Overview 为了适应不同的会计准则&#xff0c;SAP在新总账中启用了多分类账&#xff0c;&#xff08;其作用简单来说就是&#xff0c;同时一笔记账&#xff0c;会产生多个账套的凭证。&#xff09;分类账可以对应一…

一文掌握:电力管理系统该的功能和界面设计

一、什么电力管理系统 电力管理系统是一个用于监控、控制和优化电力系统运行的软件系统。它集成了实时数据采集、数据分析、决策支持和远程控制等功能&#xff0c;旨在提高电力系统的运行效率、可靠性和安全性。 电力管理系统是一个集成了数据采集、监控、分析和控制等功能的软…

应用层协议--HTTP

目录 一.HTTP是什么&#xff1f; 二.HTTP的请求和响应 a.请求&#xff1a; b.响应&#xff1a; 三.URL 四.Header 1.Host 2. Content-Length 3. Content-Type a. 请求 b. 响应 4. Referer 5. User-Agent 6. Cookie 一.HTTP是什么&#xff1f; HTTP是一种应用层协议&#xff0c…

Java并发编程-实现多线程的四种方式

创建线程的四种方式 创建线程的四种方式包括使用继承 Thread 类、实现 Runnable 接口、使用 Callable 和 Future 接口以及利用线程池。每种方式都有其特定的优势和适用场景。通过继承 Thread 类或实现 Runnable 接口&#xff0c;可以定义线程要执行的任务&#xff0c;并通过调用…

图形系统开发实战课程:进阶篇(上)——10.应用实例:交通路网

图形开发学院&#xff5c;GraphAnyWhere 课程名称&#xff1a;图形系统开发实战课程&#xff1a;进阶篇(上)课程章节&#xff1a;“图形样式”原文地址&#xff1a;https://www.graphanywhere.com/graph/advanced/2-10.html 第十章 应用实例&#xff1a;交通路网 \quad 在前面几…

Spring学习 基础(一)

Spring基础 IoC容器&#xff08;Inversion of Control&#xff09;: Spring 的核心是其控制反转&#xff08;IoC&#xff09;容器&#xff0c;它负责管理对象的生命周期和相互之间的依赖关系。通过依赖注入&#xff08;Dependency Injection&#xff09;&#xff0c;Spring能够…

python 基础知识点(蓝桥杯python科目个人复习计划59)

今日复习内容&#xff1a;做题 例题1&#xff1a;建造房屋 问题描述&#xff1a; 小蓝和小桥是两位年轻的建筑师&#xff0c;他们正在设计一座新的城市。 在这个城市中&#xff0c;有N条街道&#xff0c;每条街道上有M个位置可以建造房屋&#xff08;一个位置只能建造一个房…