V8
目前最主流的JavaScript执行引擎
即时编译,内存设限 极大地增加了运行速度
垃圾回收策略采用分代回收的思想
内存限制
64位系统下为1.4GB
32位系统下为0.7GB
根据浏览器的不同,会有些许扩容。Node情况下会有一些C++内存扩容。
为何限制?
表面原因是V8最初是作为浏览器的JS引擎而设计,不太可能遇到大量内存的场景。
深层次的原因则是由于V8的垃圾回收机制的限制,垃圾回收是阻塞式的。
垃圾回收操作会暂停JavaScript的运行,回收完毕后才会恢复执行,这种行为被称为全停顿(Stop The World)。若V8的堆内存为1.5GB,V8做一次小的垃圾回收需要50ms以上,做一次非增量式的垃圾回收甚至要1秒以上。这样浏览器将在1s内失去对用户的响应,造成假死现象。如果有动画效果的话,动画的展现也将显著受到影响。
新生代和老生代
在 V8 中,会把堆内存分为新生代和老生代两个区域。
短时间存活的新变量会存在新生代中,新生代内存极小,垃圾回收较频繁。
- 在64位系统里,新生代内存是32M,From空间和To空间各占16M
- 在32位系统里,新生代内存是16M,From空间和To空间各占8M
生存时间比较长的变量会转存到老生代,老生代占据了几乎所有内存。64位下大概是1400MB。
垃圾回收(Garbage Collection)
垃圾回收器要解决的最基本问题就是辨别需要回收的内存。
怎么判定一个变量可以回收呢?
1. 全局变量,会直到程序执行完毕,才会回收
2. 普通变量,死了(也就是当他们失去引用的时候)就可以回收
什么时候触发回收?
1. 执行完一次代码
2. 内存不够的时候
新生代回收算法
采用Scavenge算法,可以简述为复制-清空-对调,将内存平均分为两块,使用的叫From空间,闲置的叫To空间。新对象都先分配到From空间中,触发回收时先判断是否存活,把存活着的变量复制到To空间,然后把from空间清空,再对调from空间和To空间。接着继续进行内存分配,且满足晋升条件时对象会从新生代晋升到老生代。
这种算法的好处是可以提升回收速度,典型的牺牲空间换时间。
老生代回收算法
除了新生代中晋升的对象,一些大的对象会直接被分配到老生代里。因此,老生代中的对象有两个特点:占用空间大;存活时间长。由于老生代的对象比较大,若要在老生代中使用 Scavenge 算法进行垃圾回收,复制这些大的对象将会花费比较多的时间,从而导致回收执行效率不高,同时还会浪费一半的空间。
老生代的垃圾回收分为三步,标记-清除-整理:
1. 标记已死变量
2. 清除已死变量
3. 整理磁盘
对一块内存多次执行标记-清除后,会产生大量不连续的内存碎片。而碎片过多会导致大对象无法分配到足够的连续内存,于是需要整理磁盘。
新生代和老生代如何转化?
当一个对象满足某些条件,就会被移动到老生代,这称为对象的晋升。具体标准有两种:
1. 这个对象已经经历过一次回收
对象若经历过一次回收,则将对象从From空间复制到老生代中;若没有经历,则复制到To空间。
2. 新生代发现本次复制后,会占用超过25%的To空间。
当对象从From空间复制到To空间时,若To空间使用超过 25%,对象直接晋升到老生代中。设置 25% 的原因主要是因为两个空间会交换位置,如果To空间的内存太小,会影响后续的内存分配。
如何检测内存
浏览器端 window.performance.memory
Node端 process.memoryUsage()