在我们面试过程中,面试官经常会问到这么一个问题,那就是从在浏览器地址栏中输入URL到页面显示,浏览器到底发生了什么?
在讲浏览器架构之前,先理解两个概念,进程和线程;
进程是程度的一次执行过程,是一个动态概念,是程序在执行过程中分配和管理资源的基本单位,线程(thread)是CPU调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源;
简单的说呢,进程可以理解成正在执行的应用程序,而线程呢,可以理解成我们应用程序中的代码的执行器。而他们的关系可想而知,线程是跑在进程里面的,一个进程里面可能有一个或者多个线程,而一个线程,只能隶属于一个进程;
大家都知道,浏览器属于一个应用程序,而应用程序的一次执行,可以理解为计算机启动了一个进程,进程启动后,CPU会给该进程分配相应的内存空间,当我们的进程得到了内存之后,就可以使用线程进行资源调度,进而完成我们应用程序的功能;
而在应用程序中,为了满足功能的需要,启动的进程会创建另外的新的进程来处理其他任务,这些创建出来的新的进程拥有全新的独立的内存空间,不能与原来的进程内向内存,如果这些进程之间需要通信,可以通过IPC机制来进行。
很多应用程序都会采取这种多线程的方式来工作,因为进程和线程之间是互相独立的它们“互不影响”,也就是说,当其中一个进程挂掉了之后,不会影响到其他进程的执行,只需要重启挂掉的进程就可以恢复运行。
在Chrome中,主要的进程有4个:
这四个进程之间的关系是什么呢?
首先,当我们是要浏览一个网页,我们会在浏览器的地址栏里输入URL,这个时候 Browser Process 会向这个URL发送请求,获取这个URL的HTML内容,然后将HTML交给 Render Process,Render Process 解析HTML内容,解析遇到需要请求网络的资源又返回交给 Browser Process 进行加载,同时通知 Browser Process,需要 Plugin Process 加载插件资源,执行插件代码。解析完成后,Renderer Process 计算得到图像帧,并将这些图像帧交给 GPU Process,GPU Process 将其转化为图像显示屏幕。
之前的我们说到,Renderer Process 的作用是负责一个Tab内的显示相关的工作,这就意味着,一个Tab,就会有一个Renderer Process,这些进程之前的内存无法进行共享,而不同进程的内存常常需要包含相同的内容。
为了节省内存,Chrome提供了四种进程模式(Process Models),不同的进程模式会对tab进程做不同的处理。
Process-per-site-instance 兼容了性能与易用性,是一个比较中庸通用的模式
前面我们讲了浏览器的多进程架构,讲了多进程架构的各种好处,和Chrome是怎么优化多进程架构的,下面从用户浏览网页这一简单的场景,来深入了解进程和线程是如何呈现我们的网站页面的。
导航过程完成之后,浏览器进程把数据交给了渲染进程,渲染进程负责tab内的所有事情,核心目的就是将HTML/CSS/JS代码,转化为用于可进行交互的web页面。那么渲染进程是如何工作的呢?
渲染进程中,包含线程分别是:
当页面渲染完毕后,TAB内以及显示出了可交互的WEB页面,用于可以进行移动鼠标、点击页面等操作了,而当这些事件发生时候,浏览器是如何处理这些事件的呢?
点击事件从浏览器进程路由到渲染进程
浏览器的多进程架构,根据不同的功能划分了不同的进程,进程内不同的使命划分了不同的线程,当用户开始浏览网页时候,浏览器进程进行处理输入、开始导航请求数据、请求响应数据,查找新建渲染进程,提交导航,之后渲染又进行了解析HTML构建DOM、构建过程加载子资源、下载并执行JS代码、样式计算、布局、绘制、合成,一步一步地构建出一个可交互的WEB页面,之后浏览器进程又接受页面地交互事件信息,并将其交给渲染进程,渲染进程内主进程进行命中测试,查找目标元素并执行绑定的事件,完成页面的交互。
V8是谷歌推出的开源JavaScript引擎,它是用C++编写的,支持 Google Chrome、Chromiu 网络浏览器和NodeJS,它负责与环境交互并生成字节码来运行程序
V8和其他引擎之间最显着的区别是它的即时(JIT)编译器
引用计数:如果一个变量被分配了一个引用类型,那么这个对象的引用次数是+1。如果变量变为另一个值,则对象的引用数为-1,垃圾回收器将回收引用数为0的对象。但是,当对象被循环引用时,引用数永远不会归零,导致无法释放内存。标记清除:垃圾收集器首先标记内存中的所有对象,然后从根节点开始遍历,清除被引用对象和运行环境中对象的标记,剩下的标记对象不可访问,等待回收对象。
JavaScript引擎中变量的存储位置主要有两个,栈内存和堆内存。
对于不同类型的变量,栈内存和堆内存垃圾回收方式不同
新一代内存容量较小,64位系统下之后32M,新生代的记忆分为两部分:From和To。进行垃圾回收时,先扫描From,回收非存活对象,存活对象按顺序赋值到To,然后交换From/To,等待下一次回收
优化的JIT(即时编译):与C++/Java等编译型语言相比,JS是同时解释和执行的,效率低下。V8优化了这个过程:如果一段代码被执行多次,V8会将这段代码转换成机器码并缓存起来,下次运行时直接使用机器码。
隐藏类:对于C++等语言,只需几条指令就可以通过偏移获取变量信息,而JS需要进行字符串匹配,效率低下。V8借用类和偏移位置的思想,将对象划分为不同的组,即隐藏类。嵌入式缓存:即缓存对象查询的结果。一般的查询过程是:获取隐藏类地址->根据属性名找到偏移值->计算属性地址,嵌入式缓存就是这个过程结果的缓存。
每当讨论JavaScript的工作原理时,都会谈论事件循环、微任务、宏任务和回调队列。然而,所有这些东西都没有在JavaScript中实现。相反,它们是V8引擎的一部分,负责优化JavaScript代码。