目录
前言
每种语言与内存之间的关系都是不同的,了解JavaScript与内存的关系能够很好的帮助我们深入了解这门语言。
一、什么是内存?
内存其实就是程序真正运行的地方,其实程序本质上就是指令和数据的集合,所以说内存是指令和数据的存储器,之后的操作由cpu对内存中的指令和数据进行处理来完成;
1、内存的分类
1)、栈内存
栈是一种常见的数据结构,栈只允许在一端操作数据,所有数据都遵循后进先出的原则;
栈内存就使用了栈的结构,是一段连续的内存空间;
得益于栈结构的简单高效,栈内存的访问和操作速度都非常快;
要注意,栈内存的容量较小,主要是用于存放函数调用信息和变量等数据,大量的内存分配操作会导致栈溢出;
栈内存的数据存储基本都是临时性的,数据在使用完之后会被立即回收;
由栈保存的原始数据是不可更改的;
总而言之,栈内存适合放生命周期短、占用空间小且固定的数据;
栈内存由操作系统直接管理,所以栈内存的大小也由操作系统决定;
通常来说,每一条线程都会有独立的栈内存空间,默认1mb;
2)、堆内存
堆内存没有使用堆的数据结构;
堆内存是一大片内存空间,对内存的分配是动态且不连续的,程序可以按需申请堆内存空间;
要注意的是,堆内存的访问速度要比栈内存慢不少;
堆内存里的数据可以长时间的存在,无用的数据需要程序主动去回收,如果大量无用数据占用内存就会造成内存泄露;
总而言之,堆内存适合放置生命周期长,占用内存较大或者占用空间不固定的数据;
通常来说,一个进程只会有一个堆内存,同一个进程下的多个线程会共享同一个堆内存;
3)、我们为什么不能在栈中存储对象的主体呢?
对于栈来说,它的功能除了保存变量以外,还需要创建并切换函数执行上下文,如果采用栈类存储对象数据,那么切换上下文的开销也会变得巨大;
2、如何来使用内存?
不管什么语言,其运行都依赖内存,内存的生命周期基本是一致的:
1)、分配内存
2)、使用内存
3)、不需要时将内存销毁
在JavaScript中,第一步和第三步由js引擎完成;
1)、JavaScript的内存模型
JavaScript的数据类型分为基本类型和引用类型两类;
基本类型变量:标识符与值都存放在栈内存中(数据大小固定,由系统自动分配内存空间)
引用类型变量:栈内存中存放标识符与指向堆内存中值的地址,堆内存中存放具体值(数据可变,例如对象可以随意增删属性,分配内存的大小取决于代码)
需要注意的是,全局变量以及被闭包引用的变量都储存在堆内存中;
2)、值的传递
我们可以理解为JavaScript变量的拷贝都是按栈内存内的值传递,这里栈内存内的值对于基本变量来说就是其值,对于引用类型来说就是一个指向堆内存中实际值的地址;
3)、栈内存与原始类型
当我们更改普通类型变量的值时,实际上会再激活一块新的内存来储存新的值,并将变量指向新的内存空间;
当我们将一个普通类型变量赋值给另一个新的变量,也就是赋值变量时,在栈中也是会重新激活一块内存;
所以,栈内存中保存呃原始值一旦确定就不能被更改;
二、垃圾回收机制
垃圾回收是一种内存管理机制,就是将不再用到的内存及时释放,以防内存占用越来越高,导致卡顿甚至进程崩溃;
在js中内存的垃圾回收是由js引擎自动完成的;
实现垃圾回收的关键在于如何确定内存不在使用,也就是确定对象是否无用;
我们主要介绍引用计数、标记清除以及标记整理三种机制;
1、引用计数
引用计数是一种比较老的垃圾回收机制,现在几乎已经被淘汰掉了,但是这里还是有必要说一说的;
引用计数来确定对象是否无用的方法是看这个对象是否还在被引用;
如果没有引用指向该对象,那么这个对象就是可以被回收的;
缺点
无法处理循环引用;
就是说如果两个对象之间存在互相引用,这两个对象就无法正常被回收,例子如下;
let object1={x:object2};
let object2={x:object1};
优点
引用计数的优点在于垃圾回收的时机是可控的,对象一定会在被最后一个引用失效的时候销毁,这个在需要控制回收时机的操作中是比较好用的;
2、标记清除
在全局环境中,从根对象root开始,标记所有可以获得的对象,然后清除掉所有未标记的不可获得对象;
也就是说,标记清除确定对象的方法是 对象是否可以被获得
这个算法的效率不算高,同时容易引起内存碎片化的问题
3、标记整理
使用标记整理算法可以解决内存碎片化的问题,提高内存空间的可用性;
但是该算法的标记阶段比较耗时,可能会堵塞主线程,导致程序长时间处于无响应状态;
整体分为三个阶段:标记、整理和清除
相较于标记清除机制,标记整理增加了整理阶段:垃圾回收器会将被标记的对象往内存空间的一端移动,最后清除的时候就把聚在一起的未标记内存回收掉;
三、内存泄露
内存泄露是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果;
为了避免内存泄露,我们应该及时解除无用引用,不再需要的闭包、定时器以及全局变量等;
四、闭包与内存
上面说过了,闭包内的数据要放在堆内存中,那么这是为什么呢?
在函数或者其他局部作用域中创建的变量都是局部变量;
当一个局部变量被当前函数之外的其他函数引用,就是发生了逃逸,此时这个局部变量就不能随着当前函数的返回而被回收,这个变量必须存储在堆内存中;
五、全局变量与内存
上面也说了,全局变量也存储在堆内存中;
这就导致全局变量的访问速度远远不及局部变量,所以我们应该避免定义一些非必要的全局变量;
全局变量是永远可达的,所以全局变量永远不会被回收;
当一个全局变量不再需要的时候,一定要记得解除引用;
结束语
这篇文章看完之后,我们就该开始更深层次的学习了,加油!!!