【本周主题】第三期 - JavaScript 内存机制 javascript中的内存管理和垃圾回收

😔javascript

 

 

1、js中的内存空间(堆和栈是啥)?

如下用一段代码说明堆和栈的区别:html

栈(Stack)空间:

后进先出结构前端

早高峰的电梯,挤满了人,先进去的要想出来,后进去的是否是要先出来让路?就是这个道理吧。。。java

这样,要获取其中一个,是否是很费性能。node

存放的数据类型:c++

String、Number、Boolean、Null、Undefined 这五种基础数据类型。程序员

拷贝这些类型的数据就是拷贝一个副本面试

以及:算法

Object、Array、Function等引用类型的指针。数组

拷贝这些类型的数据是拷贝了指针一个副本,新指针和原指针仍是指向堆内存里的同一个地址。

栈跟队列区分:

队列是先进先出结构,它两边都有口。就像去火车站排队买票。第一我的先排队的,业务员第一个接待他。(业务员就是js主线程)

堆(Heap)空间:

树状结构

能够随时获取,就像书架上的书,也像苹果树上的每个苹果,想摘那个摘哪一个。就能够省点力气(不像栈,想摘最高的那个,还得把最底下的摘完才能摘。。。)

存放的数据类型:

Object、Array、Function等引用类型、闭包的变量。

池(常量池):

通常归类到栈中,存放常量。

2、stack的三种理解方式:

和学到这里的你同样,我常常搞不懂栈和调用栈究竟是真么关系?栈空间和栈内存又是否是一回事儿?

直到我拜读了阮一峰老师的博文,才渐渐清醒起来。

这里记录本身的读书笔记。想观摩原做的请跳转至:Stack的三种含义

“栈”在不一样的空间就有不一样的含义,主要有三种含义:

a、数据结构 - 后进先出 || Last in,First out || LIFO

做为一种数据的存放方式,特色是后进先出。

 

上边电梯的例子在这里不够更加形象,

能够想象成一摞叠在一块儿的一元硬币,假如一共十个,想要拿出最下边那个,计算机的处理方式就是,把上边九个依次拿开,

才能把第十个拿出来给你(不要想一块儿把前九个抬起来拿最后一个的想法,那是你不是计算机。。。)。

这种作法,在js中有些方法组合使用就是了:
push + pop:从最后边依次推动去,再从最后边拿。上边的1就是push(),2就是pop()的作法了。

b、代码运行方式 - 调用栈/执行栈(call stack)、出栈、入栈

调用栈,每个函数在调用的时候,都堆叠到一块儿(和上边的硬币同样),须要把上边的先调用了,而后销毁、出栈以后,再把下边的暴露出来进行。

好比下边这段代码的执行:

 

 

这里,foo()和bar()【包括window在js代码】运行时,js引擎就会生成执行上下文(就是我们常说的函数做用域了),这一段理解了,就能绕过不少面试题的障碍。

后期会整理到这里。等不及的你赶忙去看看吧。

c、内存空间 - 栈内存

 存放数据的一种内存区域。

系统划分出来的两个内存空间:栈和堆

区别如上所说,

栈有结构,因此要按次序存放,也能够知道每一个区的大小,超过大小就是栈溢出错误。一个线程分配一个stack,线程独占。运行结束栈被清空

堆没有结构,能够任意存放,大小也不能肯定,能够按须要增长。每一个进程分配一个heap,线程共用。运行结束对象实例继续存在,直到垃圾回收。若是没被回收,就是内存泄漏。

(stack的寻址速度快于heap?)

3、内存声明周期

一、内存分配

就是咱们声明一个变量、对象等的时候,系统就会自动给咱们的变量分配内存。

好比执行下边的代码:

var a = 10;

 

事实上,编译器会这么处理:(节选自《你不知道的js(上)》第一章 1.2.2  p7)

遇到 var a,编译器会先像做用域(由于这里是在全局做用域执行的,你能够理解为window)寻找是否已经有一个a存在同一个做用域集合(window对象)中。

若是有a,则忽略该声明,继续进行编译。

不然没有a,会要求做用域在当前做用域集合(即window对象)中声明一个新的变量,并命名为a。这个过程,就是内存分配。

二、内存使用

就是编译器读、写内存,调取变量/对象等的值的时候。

读就是获取变量值,写入就是赋值或修改变量的值。这里引入两个《你不知道的js(上)》介绍的名词

好比:

console.log(a)

 

引擎在这里会有两段RHS查找(获得某某的值):

一、查找console

二、查找a的值

首先,console这个对象是在window对象上的一个属性。log是他上边的一个方法。

 而后查找a的值,由于a被建立到了栈内存,对a的取值就是内存使用

三、内存回收 && 内存释放

内存回收:

当咱们使用完一个函数,该函数就会被自动销毁。(不考虑闭包的状况)

js中有垃圾回收机制,会自动回收再也不使用的内存。

 

内存释放:

var a = null;//使用完毕,自动释放内存空间

 

4、垃圾回收 (内存回收)

 

一、垃圾回收机制

不少语言,在使用完毕后须要程序员手动释放内存,咱们应该庆幸的是,js引擎有自动的垃圾回收机制。能够自动进行内存管理,减轻了咱们的工做负担。

自动垃圾收集机制:

垃圾收集器每隔固定的时间就执行一次检查,再也不使用的值就释放其占用的内存。

 

那么问题是,垃圾回收机制怎么知道哪些内存须要回收了呢?

二、垃圾回收算法

a. 引用计数

b. 标记清除

引用计数 方法:
原理,就是引擎记录全部对象值的引用次数,若是引用次数是0,就表示没用了能够“删除”

那什么样才算没有引用了呢?
好比这里js文件中只有一行代码:

var a = [123,2];

你说a有引用吗?
我第一感受是没有的,可是看阮一峰大神的讲解,这里是还有引用的。
数组还在占用内存,变量a是一个引用。因此数组[123,2]这个变量值的引用次数是1,不能被清除

要想解除他的引用,须要执行
a = null;这样,a变成了一个null值,而数组[123,2]没有人引用他了,下一轮垃圾清理器过来的时候就会把他清除了。

因此,这么看来,咱们写完程序后还要检查有哪些值是再也不使用的,就给他指向null,剪断引用的线,释放内存空间。
尤为是在全局做用域。由于局部函数做用域有可能在没有闭包的状况下,函数执行完毕就会被自动消除。可是全局的变量,系统很难判断还有没有用,就像上边的a同样,因此咱们除了避免使用全局变量外,还有记得及时释放不得已创建的全局变量。


可是他也有他的缺点,我上边列了脑图。
出现循环引用的状况:

var div = document.createElement("div"); div.onclick = function() { console.log("click"); };

变量div有事件处理函数的引用,同时事件处理函数也有div的引用!(div变量可在函数内被访问)。一个循序引用出现了,按ie中用的引用计数算法,该部份内存无可避免地泄露了。

扩展:
ie8中,COM对象,用c++实现的组件对象模型,使用的就是引用计数方法。经常由于循环引用发生内存泄漏


标记清除 方法:(经常使用)
原理:对象是否可达。否,则被回收
从window全局对象根对象开始遍历,按期向下查找,找全部从根开始引用的对象、这些对象引用的对象。而后就知道哪些是可达到的,哪些是不可达到的(个人理解是和其余人没有联系的)
能达到的添加标识,最后没有标识的就会被内存回收,而且将以前的标记清除,下一次从新标记

这样,在循环引用的状况中,即便两者彼此互帮互助循环引用防止垃圾清除,可是,标记清除法则从根元素开始找,找不到他俩,他俩就都被清除了。

 

 

 

5、内存泄漏

2018-12-07 23:40:48 

 

前文说道,若是我们创建的变量对象在不使用时没有及时被回收,就会形成内存泄漏。

内存泄漏到底是个啥?是否是油箱被戳了个洞漏了?

就是动态分配的空间,在使用完毕后没有被释放,就会致使该内存空间一直被白白占用,直到程序结束。
危害:
想一想,电脑的空间就那么多,一直有人“占着茅坑不拉*”,内存空间就会愈来愈少,程序运行就会愈来愈慢,形成程序的卡顿效果,严重的甚至还可能致使系统的崩溃。

具体表现整理以下:(参见搜狗百科)
一、cpu资源耗尽

鼠标键盘等操做无反应、页面假死

二、进程耗尽
没有新的进程能够用了,放到浏览器里有Browser进程、插件进程、GPU进程、内核渲染进程等,若是进程耗尽,其中想开一个是否是就不可能了。

三、硬盘耗尽
机器崩溃

四、内存泄漏或者内存耗尽
很麻烦并且很差用工具定位和跟踪 - 隐式内存泄漏

内存泄漏的分类:

常发性
偶发性
一次性
隐式:
说说这个和咱们前端有关系的隐式内存泄漏,就是程序自动给咱们的变量分配了内存空间,可是直到程序结束,他们才被释放。虽然程序结束,释放了全部内存,可是若是长时期的不结束这段程序,内存也就不能及时释放。

6、项目中形成你内存泄漏的几种状况

高级前端进阶公众号文章阅读笔记

目录:

一、意外的全局变量

二、被遗忘的定时器或回调函数

三、脱离DOM的引用

四、闭包

 

一、意外的全局变量

在函数做用域中,未使用var定义的变量会在全局建立一个新的全局变量:

function foo(){
  a = 2;
 var b = '局部变量' }

此时,a没有使用var定义,就会在全局对象中建立一个a属性。

或者,在普通函数中使用this时,也就在全局对象window上建立一个属性:

function bar(){
  this.a = 2;
}

  

这种作法和上边定义那个a没什么区别(固然,在不使用new bar构造函数的状况下),this指向window(非严格模式下);

由于若是使用严格模式,this指向的是undefiend;

 

解决方法就是:
尽可能变量都定义在局部函数做用域中,而且记得使用var等变量声明一下。

另外,若是不是使用构造函数,普通函数内部也记得使用this的时候,js的文件头部加上"use strict"字样,表示使用严格模式编译。

 

若是必须使用全局变量,那么要确保使用完之后将该变量指向null或重定义。

 

二、被遗忘的计时器或回调函数

定时器setInterval

var a = fun();
setInterval(function(){
  var node = document.getElementById('node');
  if(node){
     node.innerHTML = 'test';
  }
},1000);

上例:节点node或数据不须要时,定时器setInterval依然指向这些数据,因此即便node节点被溢出,interval仍旧存活而且垃圾回收期无法回收。

解决是终止定时器。

这种循环定时器,是必定要设置关闭条件,而后将其clear而且将timer指向null

三、脱离DOM的引用:

若是把DOM存成字典(键值对)或者数组,此时,一样的dom元素存在两个引用:

一个在DOM树中,另外一个在字典中,那么未来须要把两个引用都清除。

好比:

var elements ={
  btn: document.getElementById('button'),
  img: document.getElementById('image'),
};
function doS(){
 elements.img.src = 'test/img.png';
 elements.btn.click();
}
function removeBtn(){
  document.body.removeChild(document.getElementById('button'));
}
//此时,仍旧存在一个全局的#button的引用,
//elements字典,btn元素仍旧在内存中,不能被回收

  

若是代码中保存了表格某一个<td>的引用,未来决定删除整个表格的时候,,你会认为回收器会回收除了已保存的<td>之外的其余节点?

事实上:<td>是表格的子节点,子元素和父元素是引用关系,因为代码保留了<td>的应用

致使整个表格仍待在内存中,因此保存DOM元素引用的时候要当心谨慎。

 

四、闭包

闭包的关键是匿名函数能够访问父级做用域的变量。

咱们知道,函数在调用完毕以后,会被抛出执行栈进行销毁,且函数内部的局部变量也就不存在的。

可是若是有闭包的存在,函数被抛出执行栈之后,因为闭包内部引用了父级函数做用域内部的局部变量,

这些变量就不会被销毁,而是继续占据着内存空间,严重时形成泄漏。这是闭包的特性,但也是他的缺点。

 

一样的,能够不用的时候指向null

 

7、性能优化 ——管理内存

怎么避免/处理内存泄漏?

 (高程3)一旦肯定数据再也不使用,能够手动将其值设置为null来释放其引用。 —— 解除引用。

此作法适用于全局变量和全局对象的属性。由于局部变量大多会在他们离开执行环境时自动被解除引用。

 其余的对照第六点中的对应状况寻找对应解决方法吧。

 

 

  

 

引用阅读:

【1】《阮一峰的网络日志》 博客相关文章

【2】《高级前端进阶》 公众号相关文章

【3】《垃圾回收的标记清除算法详解》 博文

【4】javascript中的内存管理和垃圾回收》小火柴的蓝色理想

【5】《内存泄漏》搜狗百科

【6】《内存机制相关面试题

【n】其余多个,,,若有雷同,那就是了。。。

相关文章
相关标签/搜索