js中的数据类型可以分为基本类型和引用类型。基本类型是存在栈内存中的,引用类型是存在堆内存中的,但是引用类型的引用还是存在栈内存中的。
基本类型:
1.声明一个变量,多次赋值就会取取最后一个值
2.可以直接复制,复制之后的内容和原内容没有什么联系,类似于开辟了一个新的空间
3.不能添加属性或者方法
引用类型
1.直接赋值给另一个变量以后相互之间的修改会互相影响对方,进而引出浅拷贝与深拷贝的问题
2.可以动态添加或删除属性/方法
栈
栈 是一种 先进后出 的数据结构,栈内存是内存中用于存放临时变量的一片内存块。当声明一个基本变量时,它就会被存储到栈内存中.而当其发生复制时,会把对应内存中的数据复制一份到新内存中
栈内存的特点:存取速度快,但不灵活,同时由于结构简单,在变量使用完成后就可以将其释放,内存回收容易实现。
堆
我们在访问引用类型时,需要在栈内存中查找 对应的地址,在去堆内存中取得真正的值,访问速度自然不及栈内存。
对引用类型进行复制时,应该把堆内存中的内容复制一遍,在将新地址赋值给新变量,这就涉及到深拷贝了
堆内存的特点:使用灵活,可以动态增加或删除空间,但是存取比较慢
垃圾回收机制
原理
找出不使用的变量,释放内存。
不使用垃圾回收机制会有什么后果
1.会消耗掉所有的可用内存(内存占用),造成系统崩溃
2.内存泄漏
什么时候触发
该过程是周期性的,每隔一个固定的时间,就会自动运行程序。
如果分配的内存非常多,回收工作也会很艰巨,确定垃圾回收时间间隔就变成了值得思考的问题
基本思路
以一个函数中局部变量的正常生命周期为例。函数中的局部变量会在函数执行时存在,当函数执行完之后,此时该变量已经不在需要,占用的内存可以释放,以供后者使用。所以垃圾回收机制需要跟踪标记变量,并判定是否使用。如何标记未使用的变量也许有不同的实现方式。但是在浏览器里面的话有两种常用的方式:标记清理和引用计数。
标记清理
垃圾回收程序运行的时候,会标记内存中存储的所有变量。然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了。随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存。
引用计数
思路是对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为1。如果同一个值又被赋给另一个变量,那么引用数加1。类似地,如果保存对该值引用的变量被其他 值给覆盖了,那么引用数减1。当一个值的引用数为0时,就说明没办 法再访问到这个值了,因此可以安全地收回其内存了。垃圾回收程序 下次运行的时候就会释放引用数为0的值的内存。
该方法无法解决循环引用问题。如:A引用B,同时B引用A,相互应用。会导致内存泄漏。
内存泄露
是指程序上,动态的分配的堆内存,由于某种原因程序未释放或无法释放,造成系统的浪费,导致程序的运行速度减慢,甚至系统崩溃等严重后果。
JavaScript 内存管理
在内存中共用户使用的内存空间分为3部分:
1.程序存储区
2.静态存储区
3.动态存储区
JavaScript 内存空间分配
栈:变量 基础数据类型,值有固定大小(闭包除外)
堆:复杂的对象 引用数据类型的大小是不固定的,引用数据类型的值保持在堆内存的变量中
池:常量
注:JavaScript不允许直接访问堆内存中的位置
实际上在操作对象的引用,而不是实际的对象
总结
- 离开作用域的值会被自动标记为可回收,然后在垃圾回收期间被删除。
- 主流的垃圾回收算法是标记清理,即先给当前不使用的值加上标记,再回来回收它们的内存。
- 引用计数是另一种垃圾回收策略,需要记录值被引用了多少次。
- 引用计数在代码中存在循环引用时会出现问题。
- 解除变量的引用不仅可以消除循环引用,而且对垃圾回收也有帮助。
- 为促进内存回收,全局对象、全局对象的属性和循环引用都应该在不需要时解除引用
垃圾回收机制触发周期
以前的浏览器会按照固定的时间间隔周期性的执行,但这对性能上是比较影响的,因为浏览器会停止响应其他操作,而一次GC可能需要几百毫秒才能完成。
现代浏览器基本都会在空闲时再去进行垃圾回收,而V8已经实现了并发标记,该技术可以让 GC 扫描和标记对象时,同时允许 JS 运行。
所以很难具体回答什么时候会执行,这个因 js 引擎不同而策略不一样。