【前端知识】内存泄漏与垃圾回收机制 (上)

news2024/11/26 10:31:37

【前端知识相关分享】内存泄漏与垃圾回收机制 (上)

  • 1. 内存的生命周期
    • 1.1 内存生命周期的一般流程
    • 1.2 C++,JS和python内存分配和释放的区别
  • 2. JS中的内存管理
    • 2.1 两种数据类型与两种内存
    • 一个思考
    • 2.2 两种内存空间的区别对比
  • 3. 内存泄漏的定义、原因及危害
    • 3.1 定义
    • 3.2 危害
    • 3.3 可能的原因
      • 3.3.1 滥用全局变量,隐式的全局变量
      • 3.3.2 没有正确的销毁定时器,闭包,回调函数等
      • 3.3.3 没有正确的监听和销毁事件
      • 3.3.4 DOM清空时,还存在引用(存在没有清理的DOM引用)
      • 3.3.5 被遗漏的Map对象和Set对象
      • 3.3.6 未被清理的Console输出
  • 4. 内存怎么分析and内存泄漏如何定位
    • 4.1 内存分析的方法
      • Chrome Devtools中Memory工具的简单介绍
    • 4.2 内存泄漏的识别
      • 4.2.1 浏览器方法
      • 4.2.2 命令行方法
  • 5. 笔记篇章下

五一假期第一天,随机分享自己关于前端得一份笔记(上篇)吧。
本文为原创,未经同意请勿转载

由于篇幅有点长,所以笔者将我关于这部分的笔记分为上下两个篇章(文章后面附录下篇链接),避免读者的阅读疲倦感😵,同时也方便大家的阅读啦🤗。下面的笔记笔者也结合了自己所学的东西和平常使用的情况进行了相关的拓展。如果下面笔记中存在错误,欢迎大家及时指正,学习与讨论。

  • 上章:介绍内存的生命周期,管理及内存泄漏的相关概念,分析内存泄漏可能的原因。
  • 下章:内存泄漏的解决方法,垃圾回收机制的相关知识,内存管理的优化等。

1. 内存的生命周期

1.1 内存生命周期的一般流程

无论什么程序语言,内存的生命周期都是一致的:内存分配→内存使用(读/写)→内存释放。其中,第二部分一般来说都是我们平常都有在显式使用的,也是明确的,但是第一和第三部分在底层语言(比如C++,C语言)等是明确的,而向JS,python这种高级语言中,大部分是隐含的。

这里笔者我主要讲一下C++,JS和python中内存分配和释放之间的区别。

1.2 C++,JS和python内存分配和释放的区别

  1. C++
    对于C++,C语言等底层语言,它们一般都包含有底层的内存管理接口,可以进行显式地请求内存分配和内存释放,以此避免内存泄漏。比如:
    (1)对于内存分配,C++提供了new操作符和malloc()函数
    (2)对于内存释放,C++有delete操作符和free()函数来释放已分配的内存
    因此,一旦动态内存分配被申请且不使用时,C++并没有垃圾回收机制负责自动释放内存,而是由程序员负责手动进行内存管理。

  2. JS
    在Javascript中,创建变量是会自动分配内存,并在不再使用时自动释放内存。也就是说JS的内存分配和内存释放是通过JS引起进行自动垃圾回收的。因此,JS中没有手动释放内存的概念,因此无法显式的进行内存释放的操作,javascript没有手动释放内存的概念,因此无法进行显式的内存释放。因此,在写代码时,我们需要格外注意是否可能导致内存泄漏的问题。

  3. python
    python在内存分配方面具有类似于JS的动态类型语言的特点。当创建一个新对象时,python会从堆栈中分配内存,而且这个内存分配是由python解释器自动执行的。同时python跟JS类似,也有自动垃圾回收机制。它采用的是引用计数的垃圾回收算法来实现内存的自动释放。但引用计数会存在无法处理循环引用的垃圾回收问题,因此,python中还使用了一种循环收集的算法来处理循环引用的情况。通过上述两种算法,python实现了自动的垃圾回收,以确保内存不会被泄漏。

💡 一些小小的总结
虽然像JS,python这些高级编程语言存在自动的垃圾回收GC机制,但是由于程序本身的逻辑问题,外部的因素,或者在处理大型数据集和复杂的应用程序时,内存泄漏是难以避免的。在这种情况下,我们需要进行手动的进行内存管理,以此避免内存占用过多导致程序崩溃。比如在深度学习中,我们训练模型时,经常有时候就会遇到out of memory的问题,在受限于显卡大小的情况下,我们其实可以通过内存及时释放来做优化。比如我们在test和val时,就可以利用with torch.no_grad() 来限制自动求导的占用内存,使用.detach() 让张量与计算图分离,及时释放不需要的资源。对于一些比较大的中间变量在不需要使用的时候还可以手动del删除。这样子就可以减少内存的占用。

2. JS中的内存管理

根据前面的介绍,我们知道JS会自动进行内存的分配与释放,那JS中对变量的内存分配是怎么样的?

2.1 两种数据类型与两种内存

我们也都知道在JS中数据类型主要分为两种

  • 基本数据类型:String、Number、Boolean、Undefined、Null和Symbol
  • 引用数据类型:Object、Function、Array

那为什么我们要这么区别呢?其实这跟JS对数据的内存分配有关。在JS中,它的内存空间也分为两种,分别为栈内存和堆内存。栈空间具有运行效率高的优势,而堆空间具有空间大的优势。因此,一般将简单的数据类型放在栈内存中,而复杂的数据类型值放在堆空间。具体地:

  • 对于基本数据类型的数据:其地址和具体内容都会在栈空间进行内存分配,因此,栈内存是按值引用,所以复制的方式为值传递,即复制的时一个新值,时将存储在栈上的变量值直接复制到新的内存位置上。
  • 对于引用数据类型的数据:其地址存储在栈空间而具体内容存储在堆空间,因此查找引用类型值得时候先去栈查找再去堆查找,堆内存是按指针引用,所以复制的方式是指针传递,只是复制指向堆空间对象的指针,并不会复制对象本身,相当于一个副本。
    注:全局变量以及被闭包引用的变量(即使是原始类型)均储存在堆内存中。

在JS中,可以通过逃逸分析辨别出变量需要存储的空间是什么。逃逸分析是在一种是JS引擎中自动执行的优化技术,用于确定函数内的变量是否会在函数调用结束后继续被使用。简单来说,逃逸分析就是用来分析变量作用域的机制。一个变量逃逸,也就是指它不能被局部优化掉并且需要在函数作用域外访问。在这里也需要注意一个点,因为JS是解释性语言,所以它其实跟Java的逃逸分析不一样,它不是编译时逃逸分析。

其实,在一些文章中,JS空间也还包含池得概念,但一般池会归类为栈,所以上面我就没有介绍了。总的来说就是:栈存放变量,堆存放复杂对象,池存放常量。

一个思考

🤔为什么全局变量和闭包中的数据需要存储在堆空间中,这跟变量本身的特征与堆存储的适配性有关

  • 生命周期长:全局变量和闭包中的变量具有较长的生命周期,它们的值需要在内存中一直存在,直到程序结束才会被释放。如果存储在栈空间上,当函数执行完毕时变量所占用的内存会被立即释放,导致后续访问该变量时出现错误。

  • 动态分配内存:在闭包中,变量通常通过动态分配内存来存储,并且无法确定内存的大小。此外,在 JS中,对象和数组也是动态分配内存的,它们的大小也无法预先确定。因此,将这些变量存储在堆空间上可以更好地管理和利用内存资源。

  • 多个作用域访问:与栈空间不同,堆空间的内存不受作用域的限制。因此,全局变量和闭包中的变量可以在多个作用域中进行访问,而不会出现作用域链问题。

总之,全局变量和闭包中的变量需要存储在堆空间上,主要是由于它们的生命周期通常较长,需要动态分配内存,并且在多个作用域中进行访问。

2.2 两种内存空间的区别对比

栈内存是由操作系统直接管理(分配和释放)的一段连续的内存空间,采用栈的结构进行数据的存储,结构简单直接,访问和操作的速度快,但内存容量小,主要用于存放生命周期短,占用空间小且固定的数据。其中,栈内存中存储的变量一般都是临时性的,也就是说在该变量的当前执行环境结束后就会被销毁,被垃圾回收机制回收。

堆内存是动态且不连续的内存空间,访问速度较慢,但内存容量大,主要用于存放生命周期长,占用空间大或者占用空间不固定的数据。堆内存空间的分配和释放通常由程序员手动控制。在堆空间中的变量一般不会在其当前执行环境结束后就被销毁,因为其通常存储的是引用类型,不确定其他地方是否对它还有引用。因此,堆内存中的变量只有在其所有引用结束后才会被回收。
在这里插入图片描述

3. 内存泄漏的定义、原因及危害

3.1 定义

指不再使用的内存,没有被释放,也就是没有被垃圾回收机制回收。动态开辟的内存空间,在使用结束后未释放,结果导致一致占据该内存空间,直到程序结束。

3.2 危害

程序内存空间占用大,可能导致性能变差,程序崩溃卡死,或者程序莫名挂掉的情况,比如Out of MemoryError
常见的一些表现:CPU资源耗尽、进程耗尽、硬盘耗尽、内存耗尽。

3.3 可能的原因

3.3.1 滥用全局变量,隐式的全局变量

对于全局变量,很难判断什么时候不再被使用,因此什么时候自动释放空间也很难判断,所以通常全局变量不会被回收,我们可以使用全局变量,但应该避免一些额外的全局变量和一些不太被注意到的隐式全局变量,比如下面其实就采用了隐式的全局变量:

function fn(){
  // 没有声明从而制造了隐式全局变量test1
  test1 = '字符串'
  // 函数内部this指向window,制造了隐式全局变量test2
  this.test2 = '字符串'
}
fn()

解决方法:及时置空null。比如在上面fn()之后,将test1和test2置空。

3.3.2 没有正确的销毁定时器,闭包,回调函数等

(1)遗漏的定时器
在像节流,防抖,轮询和其他常见的场景中,我们经常需要用到定时器,那JS中常见的定时器有哪些?——setTimeout,setInterval,requestAnimationFrame。这三者都是JS中进行时间相关的操作函数,其中:

  • setTimeout:倒计时定时器,在指定的间隔时间之后运行指定的代码或者函数,ES6推荐使用箭头函数来代替SetTimeout的传统回调。
  • setInterval:定时周期执行器,用于周期性地执行指定的代码或者函数,每隔一段时间就会调用一次执行函数(将其放入执行队列中,并没有指定什么时候应该执行),直到被取消。
  • requestAnimationFrame:是一个浏览器提供的API, 它可以用于执行动画(2Dcanvas动画,webGL动画等)及其他高性能应用。其要求在浏览器在下次重绘之前调用指定的回调函数来更新动画。requestAnimationFrame(callback)接受一个函数作为参数,对该函数在下次重绘之前进行回调操作。其中回调函数执行次数与浏览器的刷新频率有关,因此其与系统保持同步,一般不会出现丢帧、卡顿的情况。与setTimeout和setInterval相比,requestAnimationFrame最主要的优势在于它的CPU节能性能。因为当页面隐藏或最小化的时候,setTimeout还是会在后台运行,此时刷新动画没用,但是requestAnimationFrame和系统保持同步,页面未激活的状态下,requestAnimationFrame会停止执行;页面激活,会从上次停止的位置开始执行,这样子就可以达到节能的效果。

🤔那三者有什么区别?常用的场景?

  • 回调函数执行时机不同:从上面的描述,我们也可以看出,setTimeoutsetInterval 是在指定的延迟时间之后执行回调函数,而且延迟时间会受到浏览器其他操作的影响而可能出现丢帧现象。而requestanimationframe 是在浏览器下一次重绘之前调用的回调函数,可以保证不会出现丢帧的情况。
  • 时间精度不同requestanimationframe 的精度更高,可达到每秒60帧的刷新率(这里是跟电脑的刷新频率有关,因为电脑一般刷新频率为60Hz),而setTimeoutsetInterval 则为 4ms 左右。
  • 调用次数不同requestanimationframe 需要主动调用才会执行回调函数,而 setTimeoutsetInterval 则可以自动多次调用同一个回调函数。
  • 主要应用场景不同:根据上述的区别,这三者的主要应用场景也有所不同。requestanimationframe主要用于动画和其他需要高帧率更新 ui 等频繁重绘任务,可以保证动画的流畅度和性能,同时如果处于后台运行状态还可以节省CPU开销。setTimeout的用法常常是实现一些特殊效果或虽然是异步但并无需立即响应的任务,比如模拟延迟加载,执行一次性定时任务等。setInterval最常用于轮询应用程序和数据源,例如即时通讯、在线游戏等。

😂三者怎么不遗漏而清除?
在不需要定时器的时候应该及时的结束定时器的调用,清除定时器避免内存泄漏:

  • setTimeout:clearTimeout()
  • setInterval:clearInterval()
  • requestAnimationFrame:cancelAnimationFrame()

(2)不正当的闭包
🤔 闭包为什么会导致内存泄漏?什么样子的闭包使用会导致内存泄漏?
这里笔者我先介绍一下闭包的概念。闭包是指有权访问另一个函数作用域中的变量的函数。也就是闭包会将函数内部的局部变量带到函数外部,而且前面我们也提到了闭包中的变量是存储在堆内存中的。那堆内存就是当你对该变量的引用全部结束之后才会释放,这样子就会导致垃圾收集器不能及时地判断这些变量释放可以被收集处理了,所以闭包过多会导致内存占用而久久不能释放,从而导致内存泄漏。

当然并不是所有的闭包函数都会导致内存的泄漏。闭包导致内存泄漏的核心在于匿名函数可以访问父级作用域的变量,也就是将闭包内的局部变量带到函数外部。怎么理解,请看下面的例子:

// 例子1:内部的变量没有到外部去,JS可以自动回收该变量,不会导致内存泄漏。
function fn1(){
  let test = '字符串'
  return function(){
    console.log('不会内存泄漏')
  }
}
let fn1Child = fn1()
fn1Child()
// 例子2:内部的变量到外部去了,JS不可以自动回收该变量,可能导致内存泄漏
function fn2(){
  let test = '字符串'
  return function(){
    console.log(test) // 这里引用了,内部变量会到外部
    return test
  }
}
let fn2Child = fn2
fn2Child()

🤔 怎么解决闭包内存泄漏问题?
根据前面的讲述,其实我们也知道关键在于当内部变量使用结束后,怎么及时解除内部变量在外部的引用。其实最简单的就是null,也就是使用结束后,把外部的引用关系置空就行,比如在上面的代码fn2Child()后面加一行fn2Child = null就可以了。

3.3.3 没有正确的监听和销毁事件

(1)遗忘的事件监听器

  • 原因:当事件监听器addEventListener在组件内挂载相关的事件处理函数,而在组件销毁时不主动将其清除时,那么其中引用的变量或者函数都被认为是需要的而不会进行垃圾回收,当内存变量占用内存过多时就可能导致内存泄漏。
  • 解决方法:对Vue组件而言,只需要在beforeDestroy 组件销毁生命周期里将对应的事件监听器清除即可,也就是removeEventListener

(2)遗忘的监听者模式

  • 原因:以为Vue为例子,当我们使用监听者模式并在组件内挂载相关的事件处理函数,比如eventBus.on,而在组件销毁时不主动将其清除时,那么其中引用的变量或者函数都被认为是需要的而不会进行垃圾回收,跟前面的事件监听器一样也可能导致内存泄漏。
  • 解决方法:跟前面事件监听器一样,只需要在beforeDestroy 组件销毁生命周期里将对应的事件处理销毁便可,也就是eventBus.off

3.3.4 DOM清空时,还存在引用(存在没有清理的DOM引用)

  • 原因:在JS中,我们创建一个DOM元素时,会使用一个变量来缓存对该DOM节点元素的引用,它会一直保存在内存中,直到该引用被清理。如果我们在移除DOM节点的时候,没有同步释放缓存引用,也就是说没有完全删除相应的元素,这个变量没有被及时的销毁或者赋值为空,那么就会产生游离DOM引用,其对应的子树也不会被释放,那么浏览器无法释放这些元素占用的内存空间,这些元素也就不会被垃圾回收,因此就可能导致内存泄漏。跟前面一样解决方法就是置空所有对应的元素。
  • 例子加深理解
    <div id="root">
      <ul id="ul">
        <li></li>
        <li id="liry"></li>
        <li ></li>
      </ul>
    </div>
    <script>
      let root = document.querySelector('#root')
      let ul = document.querySelector('#ul')
      let liry = document.querySelector('#liry')
      // 这里虽然在DOM树上使用 removechild 方法从根元素中删除了 ul 元素
      // 但是由于ul变量存在,所以整个ul及其子元素都不能垃圾回收
      root.removeChild(ul)
      // 虽然这里我们进一步置空了ul变量,但是因为liry变量仍然引用了ul的子节点
      // 说明对应元素还没有完全置空,所以ul元素依旧不能被垃圾回收
      ul = null
      // 到这里,所有对应的变量都置空,无引用
      // 此时可以立即垃圾回收掉整个 ul 元素及其子元素。
      liry = null
    </script>
    

3.3.5 被遗漏的Map对象和Set对象

前面我们讲到了引用类型,但其实在JS中数据也存在类似Java那样的弱引用和强引用的区分,只不过没有像Java那样子直接的概念罢了。那具体是什么呢?什么又是强引用,什么又是弱引用。

🤔 上面讲到的引用必须等到不存在才会垃圾回收是指什么引用呢?

在JS中,当使用Map或者Set存储对象时,跟Object一样都是强引用,如果主动清除引用,可能会造成内存泄漏的情况,也就是我们上面所讲到的引用类型数据。所以前面我们所的引用数据类型其实就是“强引用”。相对于Map或者Set的“强引用”数据结构,JS中弱引用可以使用WeakMap、WeakSet和WeakRef来实现。其中,Weak Set中的对象引用是弱引用。WeakMap中的键也是弱引用(值不是)。

🤔 那么强引用和弱引用的区别是什么?
这里其实可以借助Java中直接的概念来理解:在Java中:

  • 强引用:指一个指针或者变量,它将对象所在的内存地址保存在自己的内存中。只要这个引用存在,对象就会一直存在内存中,无法被垃圾回收器回收。
  • 弱引用:指一个指向对象的指针或变量,但它不会阻止垃圾回收器回收所引用的对象。在垃圾回收机制中通常会对不可达的数据进行回收。当一个对象只被弱引用对象引用时,则会被认为时不可达(弱访问/不可访问),因此可以被垃圾回收。
  • 总结:因此,强引用和弱引用的主要区别在于其是否在使用过程中会被自动垃圾回收。

🤔 那弱引用有什么作用?
弱引用可以允许在不影响垃圾回收的情况下实现对对象集合的存储和管理,当仅身下一个对象的弱引用时,则这些对象会立即成为垃圾回收器的候选对象,并且在必要时自动回收。相比于强引用,弱引用可以避免一些内存泄漏的问题,而且可以实现缓存等功能,进行性能的优化。但需要注意的是,在JS中,当对象被回收后,其对应的弱引用并不为null,而是被置为null,因此需要在使用弱引用前判断它是否为null。比如:

let myObject = { name: "xiaobai_Ry" };
// 创建弱引用
let myWeakRef = new WeakRef(myObject);
//释放myObject的强引用
myObject = null;
//通过myWeakRef尝试访问myObject
console.log(myWeakRef.deref()); 
// 上面输出:{ name: 'xiaobai_Ry' },不为null
// weakRef并未变成null, 要使用前需要进行判断
if (myWeakRef.deref() === null) {
    console.log('对象已被清除');
} else {
    console.log('对象仍然存在');
}

🤔 WeakMap、WeakSet和Set,Map区别
Set、WeakSet都是元素的集合,而Map和WeakMap是键值对的集合。这几种都是ES6中引入的数据类型。下面分别说一下它们之间的区别:

  • Set和WeakSet的区别
    相同点:都是元素集合,里面元素唯一。
    区别
    (1)垃圾回收机制不同:Set是强引用,WeakSet是弱引用。因此,Set中存储的对象在内存中被引用时不能被回收器回收;而weakset是弱引用,可以被回收器回收。
    (2)存储数据的类型不同:Set中存储的可以是任意类型的数据,包括基本类型和对象类型等;而WeakSet中的元素只能是对象。
    (3)可迭代性不同:Set是可迭代的,可以使用for…of、foreach等方法来遍历;而WeakSet是不可迭代的,不能循环,因为其成员随时可能随时被垃圾回收。
    (4)使用方法的不同:WeakSet支持的方法比较少。WeakSet不支持 size 属性, 不支持 entries(), keys() 和 values()方法,也不支持 foreach。WeakSet仅支持添加新对象到集合中并判断某个对象是否在该集合中。它没有 clear(), delete()等用于删除读取某个元素的方法。

  • Map和WeakMap的区别
    相同点:都是键值对的集合,Map和WeakMap的键必须唯一。
    区别
    (1)垃圾回收机制不同:Map使用强引用来保存添加进去的键和值,而WeakMap使用弱引用来保存键。因此,只要Map存在,其键和值就都不会被垃圾回收,而对于WeakMap,当某个键值没有强引用指向它时,该健和对应的值都会被回收。
    (2)数据类型不同:Map中键可以是任意类型的值;而WeakMap中键必须是对象(null 除外)。
    (3)可迭代性不同:Map可以迭代遍历键,可以使用for…of、foreach等方法来遍历;而WeakMap不可迭代遍历键,不能循环,因为其成员随时可能随时被垃圾回收。
    (4)使用方法的不同:WeakMap支持的方法比较少。Map有has()、get()、set()、delete()和clear()等方法,而WeakMap只有has()、get()和set()方法。另外,无法直接获取WeakMap中存储的所有键名,也无法获取其大小。Weakmap没有size属性。

3.3.6 未被清理的Console输出

在开发环境下,我们经常也会使用console.log()来输出一些数据来调试代码。但是console其实在某些情况下也可能会导致内存泄漏。只是我们平常也应该不会大量的console,所以也就不会注意到这一点。我们之所以在控制台能看到数据输出,是因为浏览器保存了我们输出对象的信息数据引用,也正是因此它也可能会造成内存泄漏。

当控制台写入大量数据时,它将占用内存并降低系统性能。如果不刷新缓冲区,这些数据可能会长时间留在内存中,直到程序结束才得以释放。在某些场景下,例如循环中频繁写入控制台输出,这些buff中的数据可能会长期存在,消耗大量内存。

为了避免这种情况,可以使用定时清空或分段输出控制台日志等方式来减少占用内存的情况,并在必要时禁用console输出。这也跟其他代码平台时一样的,比较常见的是我在matlab调试时,经常就会在控制台clear,这跟上面是一个道理。所以,开发环境下我们可以使用控制台输出来便于我们调试,但是在生产环境下,一定要及时清理掉输出(注释掉没有必要的console.log)。

4. 内存怎么分析and内存泄漏如何定位

4.1 内存分析的方法

内存怎么进行分析,其实就是借助一些辅助工具或者命令行来查看内存的占用情况,通过占用情况的分析找到内存泄漏的位置。比如在linux中,我们训练模型时,有时候也会用nvidia-smi来监控GPU使用情况。

Chrome Devtools中Memory工具的简单介绍

那在前端中,通常使用的内存分析则是浏览器开发工具(Devtools)中的Memory了。在我们平常的项目中,用的最多的是谷歌开发工具(Chrome Devtools)中的Memory工具,中文应该是叫做内存面板,如下图所示:
在这里插入图片描述

在Memory面板上,我们其实可以看到有3种记录内存的情况:

  • Heap snapshot:堆快照
  • Allocation instrumentation on timeline:内存分配时间轴
  • Allocation sampling:内存分配采样

点击1处便可以进行堆快照的内存分析,点击完,就会生成右边这样子的报告:
在这里插入图片描述
在堆快照结果页面中,可以有四种视图来观察:

  • Summary:摘要视图,按照构造函数进行分组,捕获对象和其使用内存的情况,可理解为一个内存摘要,用于跟踪定位DOM节点的内存泄漏。
  • Comparison:比较视图 ,对比某个操作前后的内存快照区别,分析操作前后内存释放情况等,便于确认内存是否存在泄漏及造成原因。
  • Containment:包含视图,探测堆的具体内容,提供一个视图来查看对象结构,有助分析对象引用情况,可分析闭包及更深层次的对象分析
  • Statistics:统计视图
    比如下面的图片就是统计视图,上面的图片就是摘要视图。
    在这里插入图片描述
    更多详细的内存分析可以查看下面这篇博客:
    博客:JavaScript 内存详解 & 分析指南
    这个博主介绍得很详细,必须点个赞👍。

4.2 内存泄漏的识别

内存泄漏的识别其实跟内存分析是一致的,通过分析内存,我们可以定位内存泄漏可能的位置。那我们一般是怎么识别内存泄漏的呢?

经验法则是:如果连续五次垃圾回收之后,内存占用的一次比一次大,就有内存泄漏。

前端中,内存泄漏识别的方法一般可以分为辅助工具法(浏览器方法)和命令行法。

4.2.1 浏览器方法

利用辅助工具,比如JProfiler,Chrome DevTools等。
谷歌开发工具DevTools,Record之后的事件日志Event log中的GC日志,记录页面活动的概况,生成可视化的分析结果,可以用Comparison视图或者filter按需查看快照之间的差异,可以从时间轴上直观的看到内存泄漏的情况。

  • 具体的操作:
    1. 打开开发者工具,选择 Performance ⾯板
    2. 在顶部勾选 Memory
    3. 点击左上角的 record 按钮
    4. 在页面上进行各种操作,模拟用户的使用情况
    5. 一段时间后,点击对话框的 stop 按钮,面板上就会显示这段时间的内存占用情况

4.2.2 命令行方法

使用 Node 提供的 process.memoryUsage 方法。比如:

console.log(process.memoryUsage());

// 输出
{ 
  rss: 27709440,		// resident set size,所有内存占用,包括指令区和堆栈
  heapTotal: 5685248,   // "堆"占用的内存,包括用到的和没用到的
  heapUsed: 3449392,	// 用到的堆的部分
  external: 8772 		// V8 引擎内部的 C++ 对象占用的内存
}

判断内存泄漏,以heapUsed字段为准。

  • Node.js查看方法
    1. 方法1:可以带上- -inspect参数,启动Node.js便可利用Chrome DevTools来查看
    2. 方法2:可借助第三方包heapdump来生成快照文件,导入谷歌开发工具之后进行Memory的快照对比
    3. 方法3:带上- - expose-gc参数,可调用global.gc()来触发垃圾回收,从而借助process.memoryUsage().heapUsed来检查内存的大小

在这里插入图片描述

5. 笔记篇章下

通过上面的知识,我们也对内存管理和内存泄漏有了一个大概的了解,所以在下篇中,笔者我将围绕内存泄漏的解决方法,什么是垃圾回收机制,垃圾回收机制常见算法展开详细的介绍以及内存管理应该如何优化。
笔记链接:【前端知识】内存泄漏与垃圾回收机制 (下)

码字不易,可能当中存在某些字体错误(笔者我没有发现),如果有错误,欢迎大家指正。🤗
在这里插入图片描述

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

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

相关文章

HMM理论学习笔记-隐马尔可夫模型的三个元素、假设和问题

文章目录 概率论基础条件概率全概公式边缘概率联合概率联合概率与边缘概率的关系贝叶斯公式&#xff08;条件联合概率&#xff09;马尔科夫链的概念 HMM简述HMM的三个元素符号定义1、状态转移概率矩阵A2、观测概率矩阵B3、初始状态概率向量π HMM的三个假设1、齐次马尔可夫假设…

netstat命令解析

一、linux系统中netstat命令的帮助信息 └──╼ $netstat -h usage: netstat [-vWeenNcCF] [<Af>] -r netstat {-V|--version|-h|--help}netstat [-vWnNcaeol] [<Socket> ...]netstat { [-vWeenNac] -i | [-cnNe] -M | -s [-6tuw] }-r, --route …

VMware12安装图解

目录 一、介绍VMware虚拟机 二、安装VMware12虚拟机 三、VMware虚拟机内部新建虚拟机 清理磁盘 一、介绍VMware虚拟机 VMware是一个虚拟机。 什么是虚拟机&#xff1f;字面意思‘虚拟’&#xff0c;那就不算是真的&#xff1b;‘机’可以理解为一台电脑或者一个电脑系统。…

SimpleDateFormat以及Date的使用

Date Date currentTime new Date(); 获取当前的时间 输出&#xff1a; System.out.println(currentTime); 就会以这样的格式输出 那我们不想要这样的格式&#xff0c;而是输出格式怎么办呢&#xff1f; SimpleDateFormat闪亮登场 食用方法&#xff1a; SimpleDateForma…

前端埋点采集到的数据怎么发送到服务端?

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 目录 1. Image请求 2. Ajax请求 3. WebSocket 连接 1. Image请求 1. 通过 Image请求将采集的数据发送到服务器。这种方式比较适合少量数据的采集&#xff0c;因为 lmage请求不需要返回任何数据…

【Python】中文乱码问题与解决方案 深入分析

一直以来&#xff0c;python中的中文编码就是一个极为头大的问题&#xff0c;经常抛出编码转换的异常&#xff0c;python中的str和unicode到底是一个什么东西呢&#xff1f; 在本文中&#xff0c;以哈来解释作示例解释所有的问题&#xff0c;“哈”的各种编码如下&#xff1a; …

数据库单实例升级

一、单实例环境,全时长二个半钟多。详细图文说明到这下载 1、停止所有oracle相关进程。 Emctlstop dbconsole Isqlplusctl stop Lsnrctl stop sqlplus /nolog sql>conn /as sysdba Connectedtoanidleinstance. sql>shutdown 然后&#xff0c;冷备份下数据库cp…

c++学习之运算符重载详解

目录 1.运算符重载的基本概念 2.重载加法运算符 3.重载运算符<<(全局函数实现) 4.重载>>&#xff08;输入&#xff09;运算符&#xff08;全局函数实现&#xff09; 5. 重载/--运算符 6.重载运算符 7.可以重载的运算符 1.运算符重载的基本概念 运算符重载&…

研读Rust圣经解析——Rust learn-16(高级trait,宏)

研读Rust圣经解析——Rust learn-16&#xff08;高级trait&#xff0c;宏&#xff09; 高级trait关联类型Type为什么不用泛型而是Type 运算符重载&#xff08;重要等级不高&#xff09;重名方法消除歧义never typecontinue 的值是 ! 返回闭包 宏自定义宏&#xff08;声明宏&…

day7 实现TCP通信

目录 函数介绍 代码实现 函数介绍 socket函数与通信域&#xff1a; #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol); -domain&#xff1a;指定通信域&#xff08;通信地址族&#xff09;&#xff1b; AF_I…

python-chatgpt自动化批量改写文章-基于gpt-3-5-turbo模型

作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 一、ChatGPT官方文档介绍&#xff1a; ChatGPT API—0.002美元&#xff0c;1000个token。比之前的GPT-3.0&#xff0c;成本直接降低了9…

vue3.2+ts错误:找不到模块“./App.vue”或其相应的类型声明。ts(2307)

报错原因&#xff1a; 未定义 .vue文件的类型&#xff0c;导致 ts 无法解析其类型&#xff0c;在vite-env.d.ts中定义后即可解决。 解决方法&#xff1a; 找到项目src目录下的vite-env.d.ts 文件&#xff0c;追加以下内容&#xff1a; declare module "*.vue" {impor…

【Python】chinese_calendar包的介绍和使用案例介绍(含代码)

一、问题引入 在我们的比赛中,我们对应的有时间数据,我们需要考虑不同时间段(例如月头、月中、月末等)产品需求量有何特性,节假日对产品需求量的影响,促销(如618、双十一等)对产品需求量的影响,季节因素对产品需求量的影响等。 但是我们的数据集中,却没有这种相关的…

PyTorch中的交叉熵函数 CrossEntropyLoss的计算过程

CrossEntropyLoss() 函数联合调用了 nn.LogSoftmax() 和 nn.NLLLoss()。 关于交叉熵函数的公式详见&#xff1a; 交叉熵损失函数原理详解 CrossEntropyLoss() 函数的计算过程可以拆解为如下四个步骤&#xff1a; 1、对输出的结果进行softmax操作,因为softmax操作可以将所有输入…

【Java基础教程】初识Java

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 **文章收录专栏&#xff1a;Java.SE&#xff0c;本专栏主要讲解运算符&#xff0c;程序逻辑控制&#xff0c;方法的使用&a…

Java实现数据压缩所有方式性能测试

目录 1 BZip方式1.1 引入依赖1.2 BZip工具类代码1.3 BZip2工具类代码 2 Deflater方式3 Gzip方式4 Lz4方式4.1 简介4.2 算法思想4.3 算法实现4.3.1 lz4数据格式2、lz4压缩过程3、lz4解压过程 4.4 Lz4-Java4.4.1 简介4.4.2 类库 5 SevenZ方式5.1 引入依赖5.2 工具类代码 6 Zip方式…

C++(继承和组合)

继承&#xff1a;public继承是一种 is-a 的关系&#xff0c;也就是每一个派生类对象都有一个基类对象 这些关系都适合用继承来表达 ----> 继承了之后父类的成员就变成了子类的一部分&#xff0c;子类对象可以直接用 组合&#xff1a; 是一种has -a&#xff08;有一个&…

GraphSAGE聚合流程计算实例

本篇中我们只讨论聚合流程&#xff0c;不考虑GraphSAGE的小批量训练等内容。 我们先来看一下GraphSAGE的聚合流程伪代码&#xff0c;之后会给出两个具体的计算例子进行说明&#xff1a; 11行中&#xff0c; N ( k ) ( u ) N^{(k)}(u) N(k)(u)表示节点u的邻居节点采样函数&…

力扣杯2023春·个人赛

文章目录 力扣杯2023春-个人赛[LCP 72. 补给马车](https://leetcode.cn/problems/hqCnmP/)模拟 [LCP 73. 探险营地](https://leetcode.cn/problems/0Zeoeg/)模拟 哈希 [LCP 74. 最强祝福力场](https://leetcode.cn/problems/xepqZ5/)二维差分 离散化扫描线 [LCP 75. 传送卷轴…