开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
javaScript 内存管理
一、内存机制
1.栈内存(stack)
栈是一种数据结构,它有先进后出的特点。内存栈中,存储着所有类型为原始数据的数据。
原始数据类型:String,Number,Boolean,undefined,null,Symbol。
let 喜羊羊 = 'clever'
let 懒羊羊 = 'lazy'
喜羊羊 = 懒羊羊 //将懒羊羊的value('lazy')赋值给了喜羊羊
console.log(喜羊羊) //'lazy'
2.堆内存(heap)
堆内存是区别于栈区、全局数据区和代码区的另一个区域。堆允许程序在运行时动态地申请某个大小的内存空间。堆内存中,存储着引用类型的数据。
引用类型数据:Object,Array,function,Set,Map,Date......
栈内存中存储着变量的名称,值则存储用16进制表示的在堆内存中的地址。
let 沸羊羊 = { age : 8 , gender : 'boy' } //沸羊羊为变量名,存贮着隐形16进制地址,地址指向堆中 { age : 8 , gender : 'boy' }
let 美羊羊 = { age : 7 , gender : 'girl' } // 同上
沸羊羊 = 美羊羊 // 赋值的是16进制地址,并非堆中的{ age : 7 , gender : 'girl' }
沸羊羊.age = 9 // 此时沸羊羊和美羊羊有共同的16进制地址,沸羊羊修改age等同于美羊羊修改age
console.log(美羊羊) //{ age : 9 , gender : 'girl'} //所以美羊羊的age变成了9.
内存回收
顾名思义,将垃圾内存进行周期性的回收,以节省内存空间。
1.引用计数(reference counting)
跟踪记录每个值被引用的次数,如果一个值引用次数是 0,就表示这个值不再用到了,因此可以将这块内存释放。
原理:每次引用加 1,被释放减 1,当这个值的引用次数变成 0 时,就将其内存空间释放。
let 暖羊羊 = { age : 10 , gender : 'girl' } //此时,暖羊羊栈值存储着16进制堆地址,引用为1
暖羊羊.age = null // 引用计数没变
let 先知 = 暖羊羊 // 先知栈中存储16进制堆地址,引用+1
暖羊羊 = {} // 暖羊羊栈中16进制地址被覆盖,引用-1 ,1
先知 = null // 先知栈中16进制地址被覆盖,引用-1 ,0
2.标记清除(现代浏览采用标记清除的方式)
标记清除指的是当变量进入环境时,这个变量标记为“进入环境”;而当变量离开环境时,则将其标记为“离开环境”,最后垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间(所谓的环境就是执行环境)。
全局执行环境:
- 最外层的执行环境
- 根据宿主的不同表示的执行环境也不一样,浏览器中为window
- 全局变量和函数都是作为 window 对象的属性和方法创建的
- 某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境只有当关闭网页的时候才会被销毁)
局部执行环境:
- 每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境,ECMAScript 程序中的执行流正是由这个方便的机制控制着。
let 小灰灰 = function(){
let age = 4
}
小灰灰() //当函数执行时,age被推入执行栈中(标记进入),当函数执行结束从执行栈中退出(age被标记)
3. V8 内存管理机制
V8引擎是驱动 Google Chrome 的 JavaScript 引擎的名称。
V8 引擎限制内存的原因
- V8最初为浏览器设计,不太可能遇到大量内存的使用场景(表层原因)
- 防止因为垃圾回收所导致的线程暂停执行的时间过长(深层原因,按照官方的说法以 1.5G 的垃圾回收为例,v8 做一次小的垃圾回收需要50毫秒以上,做一次非增量的垃圾回收需要1秒以上,这里的时间是指javascript线程暂停执行的时间,这是不可接受的, v8直接限制了内存的大小,如果说在node.js中操作大内存的对象,可以通过去修改设置去完成,或者是避开这种限制,1.7g是在v8引擎方面做的限制,我们可以使用buffer对象,而buffer对象的内存分配是在c++层面进行的,c++的内存不受v8的限制)
V8 回收策略
- v8 采用可一种分代回收的策略,将内存分为两个生代;新生代和老生代
- v8 分别对新生代和老生代使用不同的回收算法来提升垃圾回收效率
新生代垃圾回收
from和to组成一个Semispace(半空间)当我们分配对象时,先在from对象中进行分配,当垃圾回收运行时先检查 from中的对象,当obj2需要回收时将其留在from空间,而ob1分配到to空间,然后进行反转,将from空间和to空间进行互换,进行垃圾回收时,将to空间的内存进行释放,简而言之from空间存放不被释放的对象,to 空间存放被释放的对象,当垃圾回收时将to空间的对象全部进行回收。
新生代对象的晋升(新生代中用来存放,生命较短的对象,老生代存放生命较长的对象)
- 在新生代垃圾回收的过程中,当一个对象经过多次复制后依然存活,它将会被认为是生命周期较长的对象,随后会被移动到老生代中,采取新的算法进行管理。
- 在 From 空间和 To 空间进行反转的过程中,如果 To 空间中的使用量已经超过了 25%,那么就将 From 中的对象直接晋升到老生代内存空间中。
老生代垃圾回收(有 2 种回收方法)
- 老生代内存空间是一个连续的结构
1.标记清除(Mark Sweep) Mark Sweep 是将需要被回收的对象进行标记,在垃圾回收运行时直接释放相应的地址空间,红色的区域就是需要被回收的
2.标记合并(Mark Compact) Mark Compact 将存活的对象移动到一边,将需要被回收的对象移动到另一边,然后对需要被回收的对象区域进行整体的垃圾回收