1. javaScript的内存管理
像C语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()和free()。
相反,JavaScript是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放。
释放的过程称为垃圾回收。这个“自动”是混乱的根源,并让JavaScript(和其他高级语言)开发者错误的感觉他们可以不关心内存管理。
无论什么语言内存的生命周期基本是一致的:
1. 分配你所需的内存
2. 使用分配到的内存读/写
3. 再不需要时将其回收/释放
所有语言第二部分都是明确的。第一和第三部分在底层语言中是明确的,但在像JavaScript这些高级语言中,大部分都是隐含的。
2.为什么需要垃圾回收
由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。
一句话:内存有限,需求无限
3.Chrome的垃圾回收算法
先说一下GC时,对象的大致流动过程: 对象刚被声明时会被存放在新生代区域
的nursery
区域,对象经过第一次GC后存活下来的对象会被转移到intermediate
区域中,对象经过第二次GC后,如果依然存活,那么对象就会被转移至老生代区域
。
浏览器是怎么辨别一个对象是否该删除还是该保留呢? 这里通过判断当前变量对根结点的可达性来作为依据。
总结:
对象生存时间短的,属于新生代。
对象生存时间久的属于老生代。
活动对象:根结点可达
非活对象:根结点对其不可达
下面开始详细介绍。
3.1 新生代垃圾收集器
在javascript中,任何对象的声明分配到的内存,将会先被放置在新生代中,而因为大部分对象在内存中存活的周期很短,所以需要一个效率非常高的算法。
- 新生代中使用scavenge算法进行垃圾回收
scavenge 是一个复制算法。他会把当前A区域复制出来到B区域, 然后在B区域清理非活动对象并排序,结束后再进行赋值操作 把 A = B 一下
-
新生代区域一般只有1-8MB的空间。
-
对象晋升现象
每经过一次GC存活下来的对象就会晋升一次,直到转移到老生代区域下:nursery -> intermediate -> 老生代
3.2 老生代垃圾收集器
新生代空间中的对象在满足一定条件后会被转移到老生代区域。老生代使用 Mark-Sweep(标记清除) 和 Mark-Compact(标记整理) 算法进行垃圾回收工作。
3.2.1 mark-sweep 标记清除算法
Mark-Sweep处理时分为两阶段,标记阶段和清理阶段。在老生代中活动对象占大多数,所以Mark-Sweep在标记了活动对象和非活动对象之后,直接把非活动对象清除。
- 标记阶段:对老生代进行第一次扫描,标记活动对象
- 清理阶段:对老生代进行第二次扫描,清除未被标记的对象,即清理非活动对象
内存本身是一段连续的存储空间,在经过标记清除后,内存会变得不连续,被清除的对象遍布于各内存地址,产生很多内存碎片。所以引出了 mark-compact概念。
3.2.2 mark-compact
由于mark-sweep结束后会在老生代区域中产生很多的内存碎片,当需要分配一个很大的对象时就会出现问题,所以提出了mark-compact概念,在标记清除工作结束后对内存碎片进行整理,将所有的活动对象往一端移动,移动完成后,直接清理掉边界外的内存。
4. stop-the-world
避免内存资源竞争导致的不一致性问题。
由于垃圾回收是在js引擎中进行,而mark-compact在标记期间会移动对象,当活动对象很多时,这个过程就会非常的耗时,为了避免JavaScript应用逻辑和垃圾回收器的内存资源竞争导致的不一致性问题,垃圾回收器会将JavaScript应用暂停。如果老生代中的活动对象较多,垃圾回收器就会暂停主线程较长的时间,使得页面变得卡顿。
5. 优化 Orinoco
对垃圾回收进行优化,降低主线程的挂起时间
5.1 增量标记
穿插在主线程的各个阶段,对活动/非活动对象进行标记,只做标记动作
5.2 懒性清理
达到了阀值就会触发一次惰性清理,增量标记与惰性清理的出现,使得主线程的停顿时间大幅度减少,让用户与浏览器交互过程变得流畅了许多。
从实现机制上,由于每个小的增量标价之间执行了JavaScript代码,堆中的对象指针可能发生了变化,需要使用写屏障技术来记录这些引用关系的变化,所以也暴露出来增量标记的缺点:
- 并没有减少主线程的总的暂停时间
- 由于写屏障机制的成本,会应用增大开销
5.3 并发
并发式GC在标记时不需要挂起主线程,只有在个别时候需要短暂停下来让垃圾回收器做一些特殊的操作。存在指针同步问题。需要写屏障机制。
5.4 并行
一次GC可以交给多个线程同时进行,同样引入了写屏障机制。
6. V8当前垃圾回收机制
2011年,Chrome引入了增量标记。2018年Chrome64和node V10启动了并发标记。同时引入了多线程带来的同步问题。
6.1 副垃圾回收器
V8在新生代垃圾回收中,使用并行机制,在整理排序阶段,启用多个辅助线程,并行的进行整理。
6.2 主垃圾回收器
V8在老生代垃圾回收中,堆中内存超过某个阀值后会启用并发标记,辅助线程追踪对象的指针和引用。过程中如果对象发生了变更,写入屏障技术会对指针进行跟踪。当并发标记完成或内存达到极限时,主线程开始接手工作:根据根集进行check动作,check结束后进行GC。
课后作业:GC相关的名词列表:
新生代区域;
nursery;
intermediate;
老生代区域;
对象晋升;
mark-sweep 标记清除;
mark-compact 标记整理;
stop-the-world 全停顿;
优化orinoco;增量标记;
惰性清理;
并发concurrent;
并行parallel;
副垃圾收集器;
主垃圾收集器;
活动对象/非活动对象;
垃圾回收过程;