4类 JavaScript 内存泄漏及如何避免

SageMath下载与安装 SageMath的下载与安装 阅读详情

本文将探索常见的客户端 JavaScript 内存泄漏,以及如何使用 Chrome 开发工具发现问题。

简介

内存泄漏是每个开发者最终都要面对的问题,它是许多问题的根源:反应迟缓,崩溃,高延迟,以及其他应用问题。

什么是内存泄漏?

本质上,内存泄漏可以定义为:应用程序不再需要占用内存的时候,由于某些原因,内存没有被操作系统或可用内存池回收。编程语言管理内存的方式各不相同。只有开发者最清楚哪些内存不需要了,操作系统可以回收。一些编程语言提供了语言特性,可以帮助开发者做此类事情。另一些则寄希望于开发者对内存是否需要清晰明了。

JavaScript 内存管理

JavaScript 是一种垃圾回收语言。垃圾回收语言通过周期性地检查先前分配的内存是否可达,帮助开发者管理内存。换言之,垃圾回收语言减轻了“内存仍可用”及“内存仍可达”的问题。两者的区别是微妙而重要的:仅有开发者了解哪些内存在将来仍会使用,而不可达内存通过算法确定和标记,适时被操作系统回收。

JavaScript 内存泄漏

垃圾回收语言的内存泄漏主因是不需要的引用。理解它之前,还需了解垃圾回收语言如何辨别内存的可达与不可达。

Mark-and-sweep

大部分垃圾回收语言用的算法称之为 Mark-and-sweep 。算法由以下几步组成:

  1. 垃圾回收器创建了一个“roots”列表。Roots 通常是代码中全局变量的引用。JavaScript 中,“window” 对象是一个全局变量,被当作 root 。window 对象总是存在,因此垃圾回收器可以检查它和它的所有子对象是否存在(即不是垃圾);
  2. 所有的 roots 被检查和标记为激活(即不是垃圾)。所有的子对象也被递归地检查。从 root 开始的所有对象如果是可达的,它就不被当作垃圾。
  3. 所有未被标记的内存会被当做垃圾,收集器现在可以释放内存,归还给操作系统了。

现代的垃圾回收器改良了算法,但是本质是相同的:可达内存被标记,其余的被当作垃圾回收。

不需要的引用是指开发者明知内存引用不再需要,却由于某些原因,它仍被留在激活的 root 树中。在 JavaScript 中,不需要的引用是保留在代码中的变量,它不再需要,却指向一块本该被释放的内存。有些人认为这是开发者的错误。

为了理解 JavaScript 中最常见的内存泄漏,我们需要了解哪种方式的引用容易被遗忘。

 

三种类型的常见 JavaScript 内存泄漏

1:意外的全局变量

JavaScript 处理未定义变量的方式比较宽松:未定义的变量会在全局对象创建一个新变量。在浏览器中,全局对象是 window 。

真相是:

函数 foo 内部忘记使用 var ,意外创建了一个全局变量。此例泄漏了一个简单的字符串,无伤大雅,但是有更糟的情况。

另一种意外的全局变量可能由 this 创建:

在 JavaScript 文件头部加上 ‘use strict’,可以避免此类错误发生。启用严格模式解析 JavaScript ,避免意外的全局变量。

全局变量注意事项

尽管我们讨论了一些意外的全局变量,但是仍有一些明确的全局变量产生的垃圾。它们被定义为不可回收(除非定义为空或重新分配)。尤其当全局变量用于临时存储和处理大量信息时,需要多加小心。如果必须使用全局变量存储大量数据时,确保用完以后把它设置为 null 或者重新定义。与全局变量相关的增加内存消耗的一个主因是缓存。缓存数据是为了重用,缓存必须有一个大小上限才有用。高内存消耗导致缓存突破上限,因为缓存内容无法被回收。

2:被遗忘的计时器或回调函数

在 JavaScript 中使用 setInterval 非常平常。一段常见的代码:

此例说明了什么:与节点或数据关联的计时器不再需要,node 对象可以删除,整个回调函数也不需要了。可是,计时器回调函数仍然没被回收(计时器停止才会被回收)。同时,someResource 如果存储了大量的数据,也是无法被回收的。

对于观察者的例子,一旦它们不再需要(或者关联的对象变成不可达),明确地移除它们非常重要。老的 IE 6 是无法处理循环引用的。如今,即使没有明确移除它们,一旦观察者对象变成不可达,大部分浏览器是可以回收观察者处理函数的。

观察者代码示例:

对象观察者和循环引用注意事项

老版本的 IE 是无法检测 DOM 节点与 JavaScript 代码之间的循环引用,会导致内存泄漏。如今,现代的浏览器(包括 IE 和 Microsoft Edge)使用了更先进的垃圾回收算法,已经可以正确检测和处理循环引用了。换言之,回收节点内存时,不必非要调用 removeEventListener 了。

3:脱离 DOM 的引用

有时,保存 DOM 节点内部数据结构很有用。假如你想快速更新表格的几行内容,把每一行 DOM 存成字典(JSON 键值对)或者数组很有意义。此时,同样的 DOM 元素存在两个引用:一个在 DOM 树中,另一个在字典中。将来你决定删除这些行时,需要把两个引用都清除。

此外还要考虑 DOM 树内部或子节点的引用问题。假如你的 JavaScript 代码中保存了表格某一个 <td> 的引用。将来决定删除整个表格的时候,直觉认为 GC 会回收除了已保存的 <td> 以外的其它节点。实际情况并非如此:此<td> 是表格的子节点,子元素与父元素是引用关系。由于代码保留了 <td> 的引用,导致整个表格仍待在内存中。保存 DOM 元素引用的时候,要小心谨慎。

4:闭包

闭包是 JavaScript 开发的一个关键方面:匿名函数可以访问父级作用域的变量。

代码示例:

代码片段做了一件事情:每次调用 replaceThing ,theThing 得到一个包含一个大数组和一个新闭包(someMethod)的新对象。同时,变量 unused 是一个引用 originalThing 的闭包(先前的 replaceThing 又调用了 theThing )。思绪混乱了吗?最重要的事情是,闭包的作用域一旦创建,它们有同样的父级作用域,作用域是共享的。someMethod 可以通过 theThing 使用,someMethod 与 unused 分享闭包作用域,尽管 unused 从未使用,它引用的 originalThing 迫使它保留在内存中(防止被回收)。当这段代码反复运行,就会看到内存占用不断上升,垃圾回收器(GC)并无法降低内存占用。本质上,闭包的链表已经创建,每一个闭包作用域携带一个指向大数组的间接的引用,造成严重的内存泄漏。

Meteor 的博文 解释了如何修复此种问题。在 replaceThing 的最后添加 originalThing = null 。

Chrome 内存剖析工具概览

Chrome 提供了一套很棒的检测 JavaScript 内存占用的工具。与内存相关的两个重要的工具:timeline 和 profiles。

Timeline

附图1

timeline 可以检测代码中不需要的内存。在此截图中,我们可以看到潜在的泄漏对象稳定的增长,数据采集快结束时,内存占用明显高于采集初期,Node(节点)的总量也很高。种种迹象表明,代码中存在 DOM 节点泄漏的情况。

Profiles

附图2

Profiles 是你可以花费大量时间关注的工具,它可以保存快照,对比 JavaScript 代码内存使用的不同快照,也可以记录时间分配。每一次结果包含不同类型的列表,与内存泄漏相关的有 summary(概要) 列表和 comparison(对照) 列表。

summary(概要) 列表展示了不同类型对象的分配及合计大小:shallow size(特定类型的所有对象的总大小),retained size(shallow size 加上其它与此关联的对象大小)。它还提供了一个概念,一个对象与关联的 GC root 的距离。

对比不同的快照的 comparison list 可以发现内存泄漏。

实例:使用 Chrome 发现内存泄漏

实质上有两种类型的泄漏:周期性的内存增长导致的泄漏,以及偶现的内存泄漏。显而易见,周期性的内存泄漏很容易发现;偶现的泄漏比较棘手,一般容易被忽视,偶尔发生一次可能被认为是优化问题,周期性发生的则被认为是必须解决的 bug。

以 Chrome 文档中的代码为例:

当 grow 执行的时候,开始创建 div 节点并插入到 DOM 中,并且给全局变量分配一个巨大的数组。通过以上提到的工具可以检测到内存稳定上升。

找出周期性增长的内存

timeline 标签擅长做这些。在 Chrome 中打开例子,打开 Dev Tools ,切换到 timeline,勾选 memory 并点击记录按钮,然后点击页面上的 The Button 按钮。过一阵停止记录看结果:

附图3

两种迹象显示出现了内存泄漏,图中的 Nodes(绿线)和 JS heap(蓝线)。Nodes 稳定增长,并未下降,这是个显著的信号。

JS heap 的内存占用也是稳定增长。由于垃圾收集器的影响,并不那么容易发现。图中显示内存占用忽涨忽跌,实际上每一次下跌之后,JS heap 的大小都比原先大了。换言之,尽管垃圾收集器不断的收集内存,内存还是周期性的泄漏了。

确定存在内存泄漏之后,我们找找根源所在。

保存两个快照

切换到 Chrome Dev Tools 的 profiles 标签,刷新页面,等页面刷新完成之后,点击 Take Heap Snapshot 保存快照作为基准。而后再次点击 The Button 按钮,等数秒以后,保存第二个快照。

附图4

筛选菜单选择 Summary,右侧选择 Objects allocated between Snapshot 1 and Snapshot 2,或者筛选菜单选择 Comparison ,然后可以看到一个对比列表。

此例很容易找到内存泄漏,看下 (string) 的 Size Delta Constructor,8MB,58个新对象。新对象被分配,但是没有释放,占用了8MB。

如果展开 (string) Constructor,会看到许多单独的内存分配。选择某一个单独的分配,下面的 retainers 会吸引我们的注意。

附图5

我们已选择的分配是数组的一部分,数组关联到 window 对象的 x 变量。这里展示了从巨大对象到无法回收的 root(window)的完整路径。我们已经找到了潜在的泄漏以及它的出处。

我们的例子还算简单,只泄漏了少量的 DOM 节点,利用以上提到的快照很容易发现。对于更大型的网站,Chrome 还提供了 Record Heap Allocations 功能。

Record heap allocations 找内存泄漏

回到 Chrome Dev Tools 的 profiles 标签,点击 Record Heap Allocations。工具运行的时候,注意顶部的蓝条,代表了内存分配,每一秒有大量的内存分配。运行几秒以后停止。

附图6

上图中可以看到工具的杀手锏:选择某一条时间线,可以看到这个时间段的内存分配情况。尽可能选择接近峰值的时间线,下面的列表仅显示了三种 constructor:其一是泄漏最严重的(string),下一个是关联的 DOM 分配,最后一个是 Text constructor(DOM 叶子节点包含的文本)。

从列表中选择一个 HTMLDivElement constructor,然后选择 Allocation stack。

附图7

现在知道元素被分配到哪里了吧(grow -> createSomeNodes),仔细观察一下图中的时间线,发现 HTMLDivElement constructor 调用了许多次,意味着内存一直被占用,无法被 GC 回收,我们知道了这些对象被分配的确切位置(createSomeNodes)。回到代码本身,探讨下如何修复内存泄漏吧。

另一个有用的特性

在 heap allocations 的结果区域,选择 Allocation。

附图8

这个视图呈现了内存分配相关的功能列表,我们立刻看到了 grow 和 createSomeNodes。当选择 grow 时,看看相关的 object constructor,清楚地看到 (string), HTMLDivElement 和 Text 泄漏了。

结合以上提到的工具,可以轻松找到内存泄漏。

原文地址:http://web.jobbole.com/86244/

chipseeker Y叔 这一次讲解非常重要的peak注释,注释在ChIPseeker里只需要用到一个函数annotatePeak,它可以满足大家各方面的需求。 输入 当然需要我们上次讲到的BED文件,ChIPseeker自带了5个BED文件,用getSampleFiles()可以拿到文件的全路径,它返回的是个named list,我这里取第4个文件来演示。annotatePeak的输入也可以是GRanges对象,你如果用R做peak calling的话,直接就可以衔接上ChIPseeker了。 require(ChIPseeke 阅读详情

相关推荐

Win11 C盘爆满别慌!手把手教你安全迁移AppData和用户文件夹(附一键脚本)

本文针对Win11 C盘空间不足问题,提供了一套安全有效的解决方案。通过深入解析用户目录结构,指导用户利用符号链接和Robocopy工具,将AppData、下载文档等占用大户迁移至其他分区,并附带了自动化脚本,从根本上实现C盘瘦身,同时确保系统稳定和数据安全。

weixin_29231221的博客 2094

排查页面的内存泄漏

前言 总所周知,js是有垃圾回收机制的,大致分为标记清除和引用计数,目前主流浏览器都用的是标记清除。 定义 标记清除:垃圾回收器每个一段时候会扫描一次内存,这个时间很短。在扫描过程中,垃圾回收器会把内存中所有的变量加上标记,然后如果有你需要用到的内存,垃圾回收器就会把对应内存的标记删掉,之后将有标记的内存全部回收删除。这套机制就是垃圾回收机制,这个清理工作是高频操作。(通常全局变量是不会被自动回收的) 内存泄漏就是指由于疏忽或程序的某些错误造成未能释放的已经不再使用的内存的情况。简单来说就是一个变量已经不

qq_36205941的博客 1095

spyglass之功耗分析

用于功耗分析的软件除了ptpx外,spyglass也可以。ptpx的功耗分析是基于netlist(网表)进行的,一般用于分析综合后的pre功耗和P&R后的post功耗,但这已经是项目进行到比较后期了,如果后期发现功耗数据与我们目标功耗相差较大,则意味着需要功耗优化,这时有可能会将项目计划节点延迟。如果在前期designer可以获得设计模块的功耗数据,可提早优化功耗,spyglass则为designer提供了一个很好的手段去评估功耗,从而提前优化功耗。

u011974971的博客 9205

如何预防内存泄漏

一、内存泄漏的原因 我们的程序向系统申请分配了一块内存(new/malloc)给对象使用,程序使用完这块内存后没有释放(delete/free),导致这个不使用的对象一直占据内存单元,造成系统将不能再把它分配给需要的程序。 内存泄露简单来说就是,内存分配与内存释放没有做到一对一的匹配。 二、内存泄漏的危害 堆内存被不断的分配使用,没有及时回收,随着程序的运行,堆内存会慢慢的被消耗殆尽,当其他程序需要内存时,系统无法及时的分配合适的内存供其使用,将会造成程序崩溃。 三、如何预防内存泄漏 1、规范动态内存的使用

djh的博客 4793

MIPI转LVDS芯片调试实战:从Kernel到UEFI的完整点亮流程

本文详细解析了MIPI转LVDS芯片调试的全流程,从Kernel驱动开发到UEFI早期初始化,涵盖I2C通信、电源时序控制等核心挑战。通过LT9211芯片实战案例,提供设备树配置、电源时序优化及显示异常排查等实用技巧,助力工程师高效完成屏幕调试工作。

weixin_30314813的博客 452

eNSP常见故障处理

eNSP无法启动设备一直停在#井号的问题的解决方案?

lvshiliang123的博客 2万+

JavaScript 内存管理:如何避免常见的内存泄漏并提高性能

JavaScript内存管理是构建高效应用的关键。本文系统介绍了JavaScript内存管理机制,包括自动垃圾回收、堆栈内存分配原理,以及现代浏览器的标记-清除算法。重点分析了常见内存泄漏场景:意外全局变量、未清理的定时器/回调、DOM引用泄漏和闭包问题,并提供了WeakMap、WeakSet、FinalizationRegistry等解决方案。文章还涵盖性能优化策略(对象池、延迟加载)、框架特定管理(React/Vue)、Node.js环境处理,以及Chrome DevTools调试技巧。最后总结了核心原

油墨香^-^的博客 646

(五)激光线扫描-位移台标定

线激光属于主动测量方式,但是由于线激光的特性,我们只能通过提取激光中心线获取这一条线上的高度信息,那么要进行三维重建的话,就需要通过平移或者是旋转的方式,来让线激光扫描被测物体的完整轮廓,也就是整个表面。激光线的密度越高还原出来的物体越细腻,但由于数据量大,也会导致速度变慢。下面以位移台为例讲解,如何标定位移台。

FlyWine的博客 2891

JavaScript 内存泄漏的7种场景,及避漏方法

内存泄露(Memory Leaks):是指应用程序已经不再需要的内存,由于某种原因未返回给操作系统或者空闲内存池(Pool of Free Memory)。变慢、卡顿、高延迟。

大剑师兰特的GIS世界 3139

各向同性、VTI、TTI以及双相介质波场数值模拟

各位读者,大家好!在今天的文章中,我将介绍关于波场数值模拟中的各向同性、VTI(垂直横向等参数介质)、TTI(倾斜横向等参数介质)以及双相介质的基本概念。同时,我也会提供一些基础的Fortran代码片段,供大家了解和学习。

qq_38334677的博客 1710

8、多导睡眠图记录中基于级联自适应滤波器的脑电图信号噪声去除

本文提出了一种基于三级级联自适应滤波器的新型方法,用于去除多导睡眠图(PSG)记录中脑电图(EEG)信号的常见伪迹,包括50/60 Hz电源线干扰、心电图(ECG)和眼电图(EOG)伪迹。该方法采用最小均方(LMS)算法,通过分阶段滤波有效分离并衰减各类噪声,在保留EEG关键神经节律信息的同时显著提升信号质量。研究利用MIT-BIH数据库中的18份真实PSG记录进行验证,结果表明该方法具有无相位失真、实现简单、自适应能力强等优势,适用于睡眠研究及脑机接口等生物医学应用领域。

banana的博客 174

【JS】569- 如何避免这4类 JavaScript 内存泄漏?

英文原文 |https://auth0.com/blog/four-types-of-leaks-in-your-javascript-code-and-how-to-get-rid-...

pingan8787 383

​​光流结合深度学习实现物体运动方向判断

光流计算:提取每一帧视频中像素的运动信息,生成光流图。光流特征提取:通过特定的预处理,将光流图转换为深度学习模型可用的输入格式。深度学习模型设计:构建一个卷积神经网络(CNN)或其他适合的深度学习架构,利用光流特征进行运动方向分类。模型训练与推理:对网络进行训练并使用训练好的模型判断运动方向。光流与深度学习结合的运动方向判断技术在多个领域具有广泛的应用前景。通过光流提取像素级动态信息,并利用深度学习模型学习高层次特征,可以实现高精度的运动检测与方向判断。

matlab_python22的博客 321

五、JavaScript深入之4类常见内存泄漏及如何避免

垃圾回收算法 常用垃圾回收算法叫做**标记清除 (Mark-and-sweep) **,算法由以下几步组成: 1、垃圾回收器创建了一个“roots”列表。roots 通常是代码中全局变量的引用。JavaScript 中,“window” 对象是一个全局变量,被当作 root 。window 对象总是存在,因此垃圾回收器可以检查它和它的所有子对象是否存在(即不是垃圾); 2、所有的...

chern1992的博客 176

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

JavaScript深入之4类常见内存泄漏及如何避免

上篇文章详细介绍了内存回收和内存泄漏,今天我们继续这个篇幅,不过重点是内存泄漏可能发生的原因。 #垃圾回收算法 常用垃圾回收算法叫做**标记清除 (Mark-and-sweep) **,算法由以下几步组成: 1、垃圾回收器创建了一个“roots”列表。roots 通常是代码中全局变量的引用。JavaScript 中,“window” 对象是一个全局变量,被当作 root 。window 对象总是存在,因此垃圾回收器可以检查它和它的所有子对象是否存在(即不是垃圾); 2、所有的 roots

chern1992的博客 288

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

如何避免这4类 JavaScript 内存泄漏?

内存泄漏是每个开发者最终都要面对的问题,它是许多问题的根源:反应迟缓,崩溃,高延迟,以及其他应用问题。 什么是内存泄漏? 本质上,内存泄漏可以定义为:应用程序不再需要占用内存的时候,由于某些原因,内存没有被操作系统或可用内存池回收。编程语言管理内存的方式各不相同。只有开发者最清楚哪些内存不需要了,操作系统可以回收。一些编程语言提供了语言特性,可以帮助开发者做此类事情。另一些则寄希望于开发者对内存是...

W Y L 178

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

【进阶1-5期】JavaScript深入之4类常见内存泄漏及如何避免

(关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第一期,本周的主题是调用堆栈,今天是第5天。 本计划一共28期,每期重点攻克一个面试重难点,如果你还不了解本进阶计划,点击查看前端进阶的破冰之旅 如果觉得本系列不错,欢迎转发,您的支持就是我坚持的最大动力。 ...

weixin_34345753的博客 197

4类 JavaScript 内存泄露及如何避免

译者 | 涂鸦码龙译文 | http://jinlong.github.io/2016/05/01/4-Types-of-Memory-Leaks-in-JavaScript-and-H...

web前端开发 350

JavaScript内存泄漏

本文翻译自IBM的”Memory leak patterns in JavaScript”。翻译内容仅供参考,部分内容被我稍微“篡改”了,不当的地方还请大家及时指正。原文可以看这里: https://www.ibm.com/developerworks/web/library/wa-memleak/?S_TACT=105AGX52&S_CMP=cn-a-waJavaScript是一种强大的脚本语言

Rhine_he的博客 305

深入理解JavaScript闭包:避免常见的内存泄漏问题

深入理解JavaScript闭包:避免常见的内存泄漏问题

商务合作欢迎私信我 1万+

如何避免 JavaScript 中的内存泄漏?

大家好,我是CUGGZ。SPA(单页应用程序)的兴起,促使我们更加关注与内存相关的 JavaScript 编码实践。如果应用使用的内存越来越多,就会严重影响性能,甚至导致浏览器的崩溃。下面就来看看JavaScript中常见的内存泄漏以及如何避免内存泄漏。 一、什么是内存泄漏? JavaScript 就是所谓的垃圾回收语言之一,垃圾回收语言通过定期检查哪些先前分配的内存仍然可以从应用程序的其他部分“访问”来帮助开发人员管理内存。垃圾回收语言中泄漏的主要原因是不需要的引用。如果你的 JavaScript 应

JavaMonsterr的博客 1468

在JavaScript中,如何避免内存泄漏?

在JavaScript中,避免内存泄漏是确保应用性能稳定、响应迅速的重要方面。随着Web应用的日益复杂,内存管理变得尤为重要。内存泄漏会导致应用的内存使用量持续增长,最终可能引发性能问题、卡顿甚至崩溃。下面,我将详细探讨如何在JavaScript中避免内存泄漏,并列举几种常见的内存泄漏场景,同时提供相应的解决方案。

Dingdangr的博客 1083

JavaScript内存泄漏及如何避免

JavaScript 中的内存泄漏会对您的 Web 应用程序造成严重后果,例如性能不佳、内存使用率高以及浏览器崩溃等问题。在这篇博文中,我们了解了 JavaScript 中一些常见的内存泄漏原因,例如忘记事件监听器、闭包、大型数据结构以及 DOM 节点溢出。

m1024292777的博客 811
上一篇: Promise与setTimeout
CodingBleeding
博客等级 码龄10年 1粉丝 13原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值