浏览器原理与实践系列文章内容来自极客时间李兵老师《浏览器工作原理与实践》,主要是记录自己的学习过程,基于自己的理解对内容做的一些总结,包括《宏观视角下的浏览器:地址栏键入URL后会发生什么?HTML,CSS,JS又是怎么变成页面显示出来的》《JS中内存机制、数据类型、V8引擎垃圾回收原理、V8怎样执行JS代码,据此可以做哪些性能优化》《V8引擎工作原理》《事件循环系统:宏任务微任务如何有条不紊的执行?》《浏览器中的页面:重绘重排及合成?如何提高页面渲染性能》《网络协议:http1、http2、http3都有些什么优缺点?》《浏览器安全:xss及CSRF攻击有何特点?如何防御》共七篇,此为第三篇
JS是弱类型、动态语言:
JS数据类型分类:7种基本数据类型(number,string,undefined,null,boolean,bigInt,symbol)和一种引用数据类型(object,包括:Object, Function, Array ...)。
基本数据类型储存在栈空间中,引用数据类型储存在堆空间中,引用数据类型的指针储存在栈空间。
因为JavaScript引擎需要用栈来维护程序执行期间上下文的状态,如果栈空间大了话,所有的数据都存放在栈空间里面,那么会影响到上下文切换的效率,进而又影响到整个程序的执行效率。比如一个函数执行结束了,JavaScript引擎需要离开当前的执行上下文,只需要将指针下移到上个执行上下文的地址就可以了,当前执行完的函数执行上下文栈区空间全部回收。由于栈空间设计比较小,容易栈溢出,用于存放基本数据类型的小数据,堆空间很大,就可以存储很多大数据。
原始类型的赋值会完整复制变量值,而引用类型的赋值是复制引用地址。(深浅拷贝)
数据被使用之后,可能就不再需要了,这种数据称为垃圾数据。如果这些垃圾数据一直保存在内存中,那么内存会越用越多,所以需要对这些垃圾数据进行回收,以释放有限的内存空间。如果某块数据已经不需要了,却依旧保留在内存中,这种情况称为内存泄漏。
JavaScript是一门自动垃圾回收的语言,产生的垃圾数据是由垃圾回收器来释放,并不需要手动通过代码来释放。
代际假说:
V8引擎的垃圾回收策略就是建立在代际假说的基础上的。V8会把堆分为分为新生代和老生代两个区域,新生代中存放的是生存时间短的对象,老生代中存放的生存时间久的对象。对于这两块区域,V8分别使用两个不同的垃圾回收器,以便更高效地实施垃圾回收:
不论什么类型的垃圾回收器,都有一套共同的执行流程:
第一步是标记空间中活动对象和非活动对象。所谓活动对象就是还在使用的对象,非活动对象就是可以进行垃圾回收的对象。
第二步是回收非活动对象所占据的内存。其实就是在所有的标记完成之后,统一清理内存中所有被标记为可回收的对象。
第三步是做内存整理。一般来说,频繁回收对象后,内存中就会存在大量不连续空间,称为内存碎片。当内存中出现了大量的内存碎片之后,如果需要分配较大连续内存的时候,就有可能出现内存不足的情况。所以最后一步需要整理这些内存碎片,但这步其实是可选的,因为有的垃圾回收器不会产生内存碎片,比如副垃圾回收器。
副垃圾回收器主要负责新生区的垃圾回收。通常情况下,大多数小的对象都会被分配到新生区,所以这个区域虽然不大,但是垃圾回收还是比较频繁的。新生代中用Scavenge算法来处理。所谓Scavenge算法,是把新生代空间对半划分为两个区域,一半是对象区域,一半是空闲区域。
新加入的对象都会存放到对象区域,当对象区域快被写满时,就需要执行一次垃圾清理操作。
在垃圾回收过程中,首先要对对象区域中的垃圾做标记;标记完成之后,就进入垃圾清理阶段,副垃圾回收器会把这些存活的对象复制到空闲区域中,同时它还会把这些对象有序地排列起来,所以这个复制过程,也就相当于完成了内存整理操作,复制后空闲区域就没有内存碎片了。完成复制后,对象区域与空闲区域进行角色翻转,也就是原来的对象区域变成空闲区域,原来的空闲区域变成了对象区域。这样就完成了垃圾对象的回收操作,同时这种角色翻转的操作还能让新生代中的这两块区域无限重复使用下去。
由于新生代中采用的Scavenge算法,所以每次执行清理操作时,都需要将存活的对象从对象区域复制到空闲区域。但复制操作需要时间成本,如果新生区空间设置得太大了,那么每次清理的时间就会过久,所以为了执行效率,一般新生区的空间会被设置得比较小。
也正是因为新生区的空间不大,所以很容易被存活的对象装满整个区域。为了解决这个问题,JavaScript引擎采用了对象晋升策略,也就是经过两次垃圾回收依然还存活的对象,会被移动到老生区中。
主垃圾回收器主要负责老生区中的垃圾回收。除了新生区中晋升的对象,一些大的对象会直接被分配到老生区。因此老生区中的对象有两个特点,一个是对象占用空间大,另一个是对象存活时间长。
由于老生区的对象比较大,若要在老生区中使用Scavenge算法进行垃圾回收,复制这些大的对象将会花费比较多的时间,从而导致回收执行效率不高,同时还会浪费一半的空间。因而,主垃圾回收器是采用标记-清除(Mark-Sweep) 的算法进行垃圾回收的。
标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据。标记完成后直接将垃圾数据清除达到垃圾回收的目的。
标记过程和清除过程就是标记-清除算法,不过对一块内存多次执行标记-清除算法后,会产生大量不连续的内存碎片。而碎片过多会导致大对象无法分配到足够的连续内存,于是又产生了另外一种算法——标记-整理(Mark-Compact) ,这个标记过程仍然与标记-清除算法里的是一样的,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
JavaScript属于解释型语言,即在每次运行时都需要通过解释器对程序进行动态解释和执行。与之对应的还有编译型语言,在程序执行之前,需要经过编译器的编译过程,并且编译之后会直接保留机器能读懂的二进制文件,这样每次运行程序时,都可以直接运行该二进制文件,而不需要再次重新编译了。
V8执行js过程:
阅读量:2015
点赞量:0
收藏量:0