宝哥软件园

JavaScript中垃圾收集和内存泄漏示例的详细说明

编辑:宝哥软件园 来源:互联网 时间:2021-08-24

前言

程序需要内存才能运行。只要程序请求它,操作系统或运行时就必须提供内存。所谓内存泄漏,简单来说就是不再使用,没有及时释放的内存。为了避免内存泄漏,我们首先引入了Javascript垃圾收集机制。

在C和C语言中,开发人员可以直接控制内存的应用和恢复。但是,在Java、C#和JavaScript语言中,变量的内存空间的应用和释放都是由程序自己处理的,开发人员无需在意。也就是说,Javascript有自动垃圾收集机制。

一、垃圾回收的必要性

下面这段话引自《JavaScript权威指南(第四版)》

因为字符串、对象和数组没有固定的大小,所以只能在它们的大小已知时动态分配。每次JavaScript程序创建字符串、数组或对象时,解释器都必须分配内存来存储该实体。只要像这样动态分配内存,就应该释放内存,以便可以重用。否则,JavaScript解释器将消耗系统中所有可用的内存,并导致系统崩溃。

这段话解释了为什么系统需要垃圾收集。与C/C不同,JavaScript有自己的垃圾收集机制。

JavaScript垃圾收集的机制很简单:找出不再使用的变量,然后释放它们占用的内存,但是这个过程并不总是这样,因为它的开销很大,所以垃圾收集器会定期以固定的时间间隔执行。

Var a=“在波浪中划船”;Var b='前端工匠';var a=b;//重写代码A后,字符串“浪中航行”失去了引用(之前被A引用过)。系统检测到这一事实后,将释放字符串的存储空间,以便可以重复使用。

二、垃圾收集机制

垃圾收集机制如何知道哪些内存不再需要?

垃圾收集有两种方法:标记清除和引用计数。参考计数不太常见,但标记清除更常见。

1.去除标记

这是javascript中最常用的垃圾收集方法。当一个变量进入执行环境时,它被标记为“进入环境”。从逻辑上讲,进入环境的变量所占用的内存是永远无法释放的,因为只要执行流进入相应的环境,它们就可能被使用。当变量离开环境时,它被标记为“离开环境”。

当垃圾收集器运行时,它将标记存储在内存中的所有变量。然后,它删除环境中的变量和环境中的变量引用的标签。之后,标记的变量将被视为要删除的变量,因为环境中的变量不再能够访问这些变量。终于。垃圾收集器清理内存,销毁标记的值,并回收它们占用的内存空间。

让我们用一个例子来解释这个方法:

var m=0,n=19 //将m,n,add()标记为进入环境。添加(m,n) //标记a、b和c为进入环境。Console.log (n)//a、b和c被标记为离开环境并等待垃圾收集。函数add (a,b) {a var c=a b return c} 2。引用计数

所谓“引用计数”是指语言引擎有一个“引用表”,保存内存中所有资源(通常是各种值)的引用次数。如果某个值的引用次数为0,则表示该值不再使用,因此可以释放内存。

在上图中,左下角的两个值没有引用,可以释放。

如果不再需要某个值,但引用数不为0,垃圾收集机制将无法释放该内存,从而导致内存泄漏。

var arr=[1,2,3,4];Arr=[2,4,5]console.log('在波浪中航行');在上面的代码中,数组[1,2,3,4]是一个值,它将占用内存。变量arr是对该值的唯一引用,因此引用数为1。虽然在下面的代码中没有使用arr,但是它将继续占用内存。至于如何释放内存,我们后面会介绍。

在第三行代码中,如果数组[1,2,3,4]引用的变量arr得到另一个值,数组[1,2,3,4]的引用次数将减少1。此时数组[1,2,3,4]的引用次数将变为0,这意味着无法再次访问该值,因此可以回收数组占用的内存空间。

但是引用计数有一个最大的问题:循环引用

func(){ let obj 1={ };让obj 2={ };obj 1 . a=obj 2;//obj1指obj 2 obj 2 . a=obj 1;//obj2指的是obj1}当func函数完成执行时,返回值未定义,所以整个函数和内部变量都要回收,但是根据引用计数的方法,obj1和obj2的引用数都不是0,所以不会回收。

要解决循环引用的问题,最好在不使用时手动设置为null。上面的例子可以做到这一点:

obj1=nullobj2=null三、什么情况会造成内存泄漏?

虽然JavaScript会自动收集垃圾,但是如果我们的代码编写不当,变量就会一直处于“进入环境”的状态,无法回收。以下是一些常见的内存泄漏案例:

1.意外的全局变量

function foo(arg) { bar='这是一个隐藏的全局变量';}bar不会被声明并成为一个全局变量,在页面关闭之前不会被释放。

另一个意外的全局变量可能是由此创建的:

function foo() { this.variable='潜在偶然全局';}//foo调用自身,这指向全局对象(window)foo();在JavaScript文件的头部添加“使用严格”可以避免这样的错误。启用严格的模式解析JavaScript,以避免意外的全局变量。

2.忘记计时器或回调函数

var some resource=getData();setInterval(function(){ var Node=document . getelementbyid(' Node ');If(node) {//handle node和some resource node . innerhtml=JSON . string fy(some resource));}}, 1000);这样的代码非常常见。如果从DOM中移除了id为Node的元素,计时器将仍然存在。同时,由于回调函数包含了对someResource的引用,定时器外的someResource不会被释放。

3.关闭

函数bind event () {var obj=document。createelement ('XXX') obj。onclick=function(){//即使是空函数}}闭包可以维护函数中的局部变量,防止它们被释放。在上例中定义事件回调时,由于函数是在函数内部定义的,而内部函数——事件回调是指外部函数,所以形成了闭包。

//在函数bind event(){ varobj=document . create element(' XXX ')obj . onclick=onclick handler }之外定义事件处理函数//或者在定义事件处理函数的外部函数中,删除对DOM Function bind event(){ var obj=document }的引用。createelement ('XXX') obj。onclick=function(){//即使是空函数} obj=null}解决方法是在外部定义事件处理程序,释放闭包,或者删除定义事件处理程序的外部函数中对dom的引用。

4.没有清理的DOM元素引用

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

var elements={ button : document . getelementbyid(' button ')、image : document . getelementbyid(' image ')、text : document . getelementbyid(' text ')};函数Dostuff(){ image . src=' http://some . URL/image ';button . click();console . log(text . innerhtml);}函数remove button(){ document . body . remove child(document . getelementbyid(' button '));//此时仍然有#button的全局引用//elements字典。按钮元素仍在内存中,无法被GC回收。}虽然我们用removeChild移除了按钮,但我们仍然在elements对象中保留了#button的引用,换句话说,DOM元素仍然在内存中。

四、内存泄漏的识别方法

新版chrome在性能方面:

步骤:

打开开发者工具Performance,勾选截图和内存左上角的点,开始录制。停止记录图片中与Heap对应的部分,可以看到内存的周期性下降和垃圾收集周期。如果垃圾收集后的最小值(我们称之为MIN)在上升,那么一定存在严重的内存泄漏问题。

避免内存泄漏的一些方法:

减少不必要的全局变量或生命周期长的对象,及时垃圾收集无用数据,注意程序逻辑,避免“无限循环”,避免创建过多对象。总之,我们需要遵循一个原则:不用的东西要及时归还

动词(verb的缩写)垃圾收集使用场景的优化

1.阵列阵列优化

将[]赋给数组对象是清空数组的快捷方式(例如:arr=[];),但需要注意的是,这个方法创建了一个新的空对象,把原来的数组对象变成了一小块内存垃圾!事实上,将数组长度赋值为0(arr.length=0)也可以达到清空数组的目的,同时可以实现数组的重用,减少内存垃圾的产生。

const arr=[1,2,3,4];Console.log('在波浪中航行');Arr.length=0 //可以直接清空数字,数组类型保持不变。//arr=[];虽然变量A变成了一个空数组,但是一个空数组对象被重新应用到了堆上。2.尽可能重用对象

尽可能地重用对象,尤其是在循环等地方创建新对象时。未使用的对象应尽可能设置为null,并尽快回收。

Var t={} //每个循环都会创建一个新对象。for(var I=0;i 10I){//var t={ };//每个循环创建一个新对象。t . age=19t . name=' 123 ' t . index=icon sole . log(t)} t=null//如果不再使用该对象,则立即将其设置为null;等待垃圾回收。3.循环中的函数表达式可以重用,最好放在循环外。

//最好不要在循环中使用函数表达式。for(var k=0;k10;K) {var t=function(a) {//创建了10次函数对象。Console.log(a)} t(k)}//推荐用法Function t(a){ console . log(a)} for(var k=0;k10;K) {t(k)}t=空摘要

以上就是本文的全部内容。希望本文的内容对大家的学习或工作有一定的参考价值。谢谢你的支持。

更多资讯
游戏推荐
更多+