栈和堆
内存空间:栈存放原始数据类型,堆存放引用数据类型
栈
栈是一种数据结构。限定在表尾进行插入和删除操作的线性表
在js中,栈存放的数据类型有Boolean, Number, String ,Undefined ,Null,对象变量的指针
有一种说法是栈存储的东西是不会变的东西,就像null是一个对象,为什么会被放在栈内存而不是堆内存,因为null是一个不可变的对象
特点:后进先出
看一下这段代码:
var a = 10
var b = a
编译原理:
var a
,编译器先判断当前作用域是否存在a变量,如果有则忽略,否则在当前的作用域新声明一个变量a
a = 10
,引擎在执行这个赋值的时候先去找a变量是否在作用域链上是否存在,如果存在,则进行赋值操作,将10赋值给a,否则抛出异常
以上操作在栈上表现为下图:
实际就是在标识符中添加了一个变量a,在栈中开辟了一片内存,存放了一个值是数字10,a标识符对应的值是10
var b = a
var b
,编译器先判断当前作用域是否存在b变量,如果有则忽略,否则在当前的作用域新声明一个变量b
b = a
,引擎在执行这个赋值的时候先去找b变量是否在作用域链上是否存在,如果存在,则进行赋值操作,复制a的值,将值给b,否则抛出异常,
根据栈的后入先出,确定出栈顺序
假如1234是入栈顺序,那么出栈顺序可能有哪些?
4个元素的全排列共有24种,栈要求符合后进先出,按此衡量排除后即得:
1234√ 1243√ 1324√ 1342√ 1423× 1432√
2134√ 2143√ 2314√ 2341√ 2413× 2431√
3124× 3142× 3214√ 3241√ 3412× 3421√
4123× 4132× 4213× 4231× 4312× 4321√
14种可能,10种不可能,如上所示。
因为入栈顺序是确定的 但是出栈时机却是不确定的 例如1234这种出栈顺序就是1入栈 1出栈 2入栈 2出栈…
堆
是存储引用类型的地方。跟调用堆栈主要的区别在于,堆可以存储无序的数据,这些数据可以动态地增长,非常适合数组和对象。在Javascript中我们无法直接操作堆,我们在操作对象时,实际是在操作对象的引用。
var c={
name:'Bob',
age:18
}
如图
实际就是在栈中存了一个引用指针,指向堆中的这个内存
垃圾回收
1、引用计数法
记录每个值被引用的次数,当引用数为0时,表示这个值不再使用了,判定可以进行释放。
var o = {
a: {
b:2
}
};
// 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量o
// 很显然,没有一个可以被垃圾收集
var o2 = o; // o2变量是第二个对“这个对象”的引用
o = 1; // 现在,“这个对象”只有一个o2变量的引用了,“这个对象”的原始引用o已经没有
var oa = o2.a; // 引用“这个对象”的a属性
// 现在,“这个对象”有两个引用了,一个是o2,一个是oa
o2 = "yo"; // 虽然最初的对象现在已经是零引用了,可以被垃圾回收了
// 但是它的属性a的对象还在被oa引用,所以还不能回收
oa = null; // a属性的那个对象现在也是零引用了
// 它可以被垃圾回收了
但是这个引用计数法容易造成内存泄漏,看下一个例子
var a = {
name: 'a'
}
var b = {
name: 'b',
a: a
}
a.b = b
这个时候,a被b引用,b被a引用,所以这两个都不会计数等于0,所以都不会被清除
2、标记清除法(2012年起,所有浏览器均使用了此机制)
大概是这样,先把所有的变量都加上标记,当这个变量在作用域链中还能被访问,那么清除掉这个标记,当遍历完所有的变量后,如果还有这个标记的变量,那么进行回收。
主要依赖与计算环境
执行环境:定义了变量或函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有一个与之相关联的变量对象(全局对象/局部对象),环境中定义的所有变量和函数都保存在这个对象中。
当变量进入执行环境时,就标记这个变量为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到他们。当变量离开环境时,则将其标记为“离开环境”。
垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。
简单理解为:当每个变量或函数在作用域链中无法访问,那么就该收集了。
内存大小
每一个浏览器窗口都有一个V8引擎负责运行js代码,由于js初始设计是作为脚本语言,为浏览器设计,不太可能遇到大量内存的场景,所以在设计的时候约定好了它占用的内存大小
一般和电脑配置有关:
64位操作系统的,运行内存大小是1.4G
32位操作系统的,运行内存大小是0.7GB
关于内存:
网易云课堂是这么说的:
新生代内存占总内存的25%,老生代内存占总内存的75%
但是我查了一下,更多的说法是:
64新生代内存32M,老生代内存1400M
32新生代内存16M,老生代内存700M
其中新生代中又划分了From和To两个区域,各占新生代各一半的内存
大致我们一个变量从新生代到老生代的过程大概是:
我们运行程序在不停的往内存里面增加变量消耗内存,当我们新增一个变量的时候,会在新生代的From里面去开辟一个空间放这个东西,当你不停的创建变量,到From的内存达到一个内存临界值的时候(有说是内存耗完,我觉得应该是一个阈值,待验证),扫From空间进行垃圾回收,未被回收的变量做一个标记,并将From空间的未被回收的变量复制一份到To中去,清空From空间,并将To空间与From空间对调,也就是老的To空间此时变为From,此时的To空间是一个空白的空间,然后此时From空间就存在了上一次未被回收的变量对不对?并且有一个未被回收的标记,当From空间再次进行垃圾回收时,如果这些标记了未被回收的变量再次无法被回收时,这些变量会进入老生代内存空间
新生代和老生代是有差别的,新生代是牺牲内存换取时间效率。
你看新生代真正能用于存储的空间其实只有一半,不停在做From和To的垃圾回收和切换。而老生代的垃圾回收和内存机制要复杂的多。在内存中存储的数据是类似于一个有序的数组的格式,例如10001地址存放a,10002地址存放b,10003地址存放c,在新生代中,假如b被回收,那么实际就是10002会被空出来,但是你又没办法往10002里面放东西,所以产生了10002磁盘碎片,但是这样快啊,我啥都不用管我就把东西复制一遍丢给To空间,而在老生代中它会把c放到10002,释放出来10003,也就是它会帮你整理磁盘碎片,所以新生代的垃圾回收要比老生代的垃圾回收要快,但是新生代的内存利用率没有老生代的高