文章目录
💬 前言
说起
“ 内存泄漏 ”
,科班出身的卷王们应该第一时间会想到 C语言的指针,对内存的分配 或者 其他操作。程序需要运行,必然会占用内存,就好比我们在电脑上运行程序,就必须向运行的软件程序提供内存,它才能运行。
程序运行会生成对应的服务进程,对于一些持续的服务进程,它会持续的占用内存,但是当服务运行一个来回时,如果上次运行申请的内存,没有得到及时的释放,就会导致
“ 内存泄漏 ”
,
通俗点说,就是指被遗漏的内存持续占用堆积
。
这样的结果显而易见,会造成服务性能降低,严重会导致服务卡顿等现象。所以这里就需要我们去了解 “ 内存泄漏 ” 的原理,避免出现内存持续占用的情况! 接下来,就由小温带大家去了解 “ 内存泄漏 ” 相关的知识点吧!
👉 一、“ 内存泄漏 ”简述
内存泄漏(
Memory leak
)是在计算机科学中,由于疏忽或错误造成
程序未能释放已经不再使用的内存
。
并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在
释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费
正如前言所述,程序的运行需要内存。只要程序提出要求,操作系统或者运行时就必须供给内存
对于
持续运行的服务进程
,必须及时
释放
不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃
以
C语言
为例,由于 C语言 是 手动管理内存,如果程序设计不当,是非常容易导致
“ 内存泄漏 ”
。
// 申请内存char* buffer;
buffer =(char*)malloc(42);// Do something with buffer...// 使用free方法释放内存free(buffer);
上面案例中,在C语言中,使用
malloc
方法用来申请内存,使用完毕之后,必须自己用
free
方法释放内存。
这种
手动管理内存
的方式非常麻烦,在申请内存的变量多时,操作累赘重复。所以为了提高编写效率,现在大多数语言提供
自动内存管理
,减轻程序员的负担,这被称为
" 垃圾回收机制 "
。
👉 二、 垃圾回收机制
Javascript
具有自动垃圾回收机制(
GC:Garbage Collecation
),也就是说,执行环境会负责管理
代码执行过程中使用的内存
,自动进行释放等操作!
> 原理
垃圾收集器会定期(
周期性
)找出那些不在继续使用的变量,然后释放其内存。
那么,可能会有卷王疑惑了,垃圾收集器怎么知道我哪些变量还要使用,哪些变量在指定位置不需要使用了呢? 这就引出了 “
垃圾回收机制
” 的实现方式了,内容如下:
通常情况下有两种实现方式:
- 标记清除 : 通过判断变量进出执行环境,标记变量在指定位置所处的状态,若变量已离开执行所处的环境,此变量将等待 “
垃圾回收
” - 引用计数 : 通过判断变量被引用的次数,判断此变量是否任需使用。如果
引用次数为 “ 0 ”
,那么此变量占用的内存将会被回收!
> 实现方式(详)
① 标记清除
判断原理 : 当变量进入执行环境时,就标记这个变量为“
进入环境
“。进入环境的变量所占用的内存就不能被释放,当变量离开环境时,则将其标记为“
离开环境
“
垃圾回收程序运行的时候,会标记内存中存储的所有变量。然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。
在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了
随后
垃圾回收程序
做一次内存清理,销毁带标记的所有值并收回它们的内存。
优点:
- 实现简单,标记情况无非是打与不打的两种情况,通过二进制(0和1)就可以为其标记。
- 能够回收循环引用的对象
- 是
v8
引擎使用最多的算法。
缺点:
在清除垃圾之后,剩余对象的内存位置是不变的,就会导致空闲内存空间不连续。这样就出现了
内存碎片
,并且由于剩余空间不是整块,就需要考虑内存分配的问题。
举个例子:
var m =0,n =19// 把 m,n,add() 标记为进入环境。add(m, n)// 把 a, b, c标记为进入环境,当此方法体运行完,代表内部的局部变量离开环境。
console.log(n)// a,b,c标记为离开环境,等待垃圾回收。functionadd(a, b){
a++let c = a + b
return c
}
② 引用计数
语言引擎有一张"
引用表
",保存了内存里面所有变量引用的内存资源(通常是各种值)的引用次数。如果一个值的引用次数是
0
,就表示这个值不再用到了,因此可以将这块内存释放。
tips: 如果一个值不再需要了,引用数却不为0,垃圾回收机制无法释放这块内存,从而导致内存泄漏。
const arr =[1,2,3,4];// arr 占用的内存泄漏
console.log('hello world');
上面代码中,数组arr, 存储的 [1, 2, 3, 4] 是一个值,会占用内存。变量arr是仅有的对这个值的引用,因此引用次数为
1
。尽管后面的代码没有用到arr,它还是会持续占用内存。
如果需要这块内存被垃圾回收机制释放,只需要设置如下:
arr =null
通过设置arr为null,就解除了对数组[1,2,3,4]的引用,引用次数变为 0,就会立刻被垃圾回收了。
优点:
- 引用计数为零时,发现垃圾立即回收
- 最大限度减少程序暂停
缺点:
- 无法回收循环引用的对象
- 空间开销比较大
👉 三、常见的几种内存泄漏的情景
①在某个局部代码块中,出现全局变量操作
functionfoo(arg){
bar ="this is a hidden global variable";}
② 全局变量可能由 this 创建
functionfoo(){this.variable ="potential accidental global";}// foo 调用自己,this 指向了全局对象(window)foo();
tips:上述使用严格模式,可以避免意外的全局变量
③ 定时器也常会造成内存泄露
var someResource =getData();setInterval(()=>{var node = document.getElementById('Node');if(node){// 处理 node 和 someResource
node.innerHTML =JSON.stringify(someResource));}},1000);
如果id为Node的元素从DOM中移除,该定时器仍会存在,同时,因为回调函数中包含对
someResource
的引用,定时器外面的
someResource
也不会被释放
④ 包括之前所说的闭包,维持函数内局部变量,不能及时释放,一样会造成内存泄漏
functionbindEvent(){var obj = document.createElement('XXX');varunused=function(){
console.log(obj,'闭包内引用obj obj不会被释放');};
obj =null;// 解决方法}
⑤ 没有清理对DOM元素的引用同样造成内存泄露
const refA = document.getElementById('refA');
document.body.removeChild(refA);// dom删除了
console.log(refA,'refA');// 但是还存在引用能console出整个div 没有被回收
refA =null;
console.log(refA,'refA');// 解除引用
**⑥ 使用事件监听
addEventListener
监听的时候,在不监听的情况下使用
removeEventListener
取消对事件监听**
以上为日常开发中,比较常见的内存泄漏场景,需要在平时开发中加以留意!
> 小结
虽然JavaScript提供了一套
垃圾回收机制
, 但是并不代表不用关注
内存泄露
。那些很占空间的值,一旦不再用到,需要及时检查是否还存在对它们的引用。如果是的话,就必须手动解除引用,这是日常开发中,需要注意的JavaScript书写规范!
今天的内容就此结束,如果觉得对你有帮助的话,不妨点点赞,支持一下小温呀!
📃 参考文献
- JavaScript 内存泄漏教程 - 阮一峰
- 详解JavaScript的垃圾回收机制 - 1900’‘’'s 88 keys
往期内容 💨
🔥 < 今日份知识点:浅述对 “ Vue 插槽 (slot) ” 的理解 以及 插槽的应用场景 >
🔥 <恢复更新进度ing:今天浅聊一下前端CSS样式 及 书写的规范 >
🔥 < 每日份知识快餐:axios是什么?如何在Vue中 封装 axios ? >
🔥 < 今日份知识点:web常见的攻击方式(网络攻击)有哪些?如何预防?如何防御呢 ? >
版权归原作者 技术宅小温 所有, 如有侵权,请联系我们删除。