深入了解浏览器

470 阅读17分钟

进程与线程

进程 progress

简介

  • 进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。
  • 进程是一种抽象的概念,从来没有统一的标准定义。
  • 进程之间的通讯叫IPC (InterProcess Communication),指在不同进程之间传播或交换信息。

分类

  • 程序、数据集合、进程控制块组成。
  • 内存空间(代码,数据,进程空间,打开的文件)和一个或多个线程组成
  • 进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块包含进程的描述信息和控制信息是进程存在的唯一标志

特征

  • 动态性:进程是程序的一次执行过程,是临时的,有生命期的,事动态产生的,动态消亡的。
  • 并发性:任何进程都可以同其他进程一起并发执行。
  • 独立性:进程是系统进行资源分配和调度的一个独立单位。
  • 结构性:进程由程序、数据和进程控制块三部分组成。

线程 thread

简介

  • 线程是程序执行中一个单一的程序控制流程,是程序执行流的最小单元是处理器调度和分派的基本单位。
  • 一个进程可以有一个或多个线程,线程之间可以共享内存空间

分类

  • 线程ID、当前指令指针PC、寄存器和堆栈组成。

进程和线程的区别

  • 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
  • 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
  • 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
  • 调度和切换:线程上下文切换比进程上下文切换要快得多

浏览器架构

早期---单进程

简介

早期的浏览器都是单进程的架构,即:整个浏览器的所有模块都是在一个进程中运行。 单进程浏览器 如此多的模块运行在同一个进程中,容易导致浏览器出现许多的问题,如:不稳定、不流畅、不安全

不稳定

进程中某一个线程执行出错会导致整个浏览器崩溃

不流畅

  • 页面线程中的各个模块,是有执行顺序的,只要有一个卡顿后续的模块都会呗阻塞,导致整个浏览器无法正常运转。如果有一个js代码出现死循环,或者执行占用了较长的时间,会导致整个浏览器卡顿甚至有崩溃的风险。
  • 当一个进程关闭之后,操作系统会回收进程内所有的内存。现在一个页面关闭只是会杀死相关的线程,不会触及到进程,导致无法回收的内存越来越多,浏览器运行回出现卡顿现象。

不安全

  • 进程之间是相互隔离的,线程之间的数据是共享的。不同页面之间可以相互访问数据,造成数据泄露
  • 恶意插件、脚本可以通过浏览器漏洞获取系统的权限,进而导致电脑出现中病毒的风险,电脑中的数据页不安全了。

现代浏览器---多进程

浏览器中的进程

  • 浏览器进程。主要负责界面显示、用户交互、子进程管理,同时提供存储功能。
  • 渲染进程 核心人物是讲HTML/CSS、javascript转换成用户可以与之交互的网页,排版引擎Blink和JavaScript引擎V8都是在该进程运行。默认情况下,chrome为每个Tab标签创建一个渲染进程,出于安全考虑,渲染进程都是运行在沙箱模式下。
  • GPU进程 用于绘制页面和实现3D效果
  • 网络进程 负责页面的网络资源加载。
  • 插件进程 负责插件的运行,因为插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器页面造成影响。

解决安全问题

  • 不稳定问题:进程相互隔离,当一个页面或者插件崩溃时,只能影响当前的进程,不会影响其他页面。
  • 不流畅:JavaScript运行在渲染进程中,即使JavaScript阻塞了渲染进程,只能影响当前的渲染页面,不会影响其他页面。
  • 安全问题:采用安全沙箱,恶意程序无法获取系统权限。

缺点

  • 更高的资源占用,进程会包含一些内存资源。
  • 更复杂的体系架构,浏览器各个模块之间耦合性高、扩展性差。

浏览器相关的优化

  • chrome默认采用每个标签一个渲染进程,如果两个页面属于同一个站点,那么两个标签回使用同一个渲染进程。或者内存性能不太够时,浏览器会合并同个站点共用一个渲染进程

未来面向服务的架构

在 2016 年,Chrome 官方团队使用“面向服务的架构”的思想设计了新的 Chrome 架构。Chrome 整体架构会朝向现代操作系统所采用的“面向服务的架构” 方向发展,原来的各种模块会被重构成独立的服务(Service),每个服务(Service)都可以在独立的进程中运行,访问服务(Service)必须使用定义好的接口,通过 IPC 来通信,从而构建一个更内聚、松耦合、易于维护和扩展的系统。

Chrome 最终要把 UI、数据库、文件、设备、网络等模块重构为基础服务,类似操作系统底层服务,下面是 Chrome“面向服务的架构”的进程模型图:

从输入URL到页面展示完整流程示意图 chrome“面向服务的架构”进程模型图

浏览器渲染

输入URL

  • 首先用户从浏览器输入请求信息,地址栏会判断是关键字还是请求的url,关键字会使用浏览器默认的搜搜引擎,然后合成新的带关键字的url
  • 然后,浏览器进程会通过进程间通讯方式IPC讲url发送给网络进程。

网络请求阶段

  • DNS 解析
  • 发起TCP链接
  • 发送HTTP请求
  • 服务器处理请求并返回HTTP报文
  • 渲染阶段

渲染阶段

构建DOM树

浏览器是无法直接理解和使用HTML,需要讲HTML转换成浏览器能够理解的结构DOM树。 在浏览器开发者工具中,输入document 就可以查看DOM树 获取到html文件后,浏览器会一行一行的扫描,生成DOM树 image.png

Html解析器

  • 通过HTML解析器(HTMLParser)来将HTML字节流转换成DOM结构。
  • HTML解析器是网络加载了多少,就解析多少,并不是等整个html下载完成后再进行加载
  • http请求头中有一个content-type为text/html就表示是html文档,获取到这种格式的数据时就会进行解析。
  • html解析牵扯到网络进程和渲染进程两个进程,这两个进程之间会建立一个共享数据的管道,使得html解析器可以获取字节流用来解析

dom解析工作

首先运用分词器将字节流转换成Token(就是tag和文本token,即:标签和里面的文本内容),html解析器有一个token栈用了解析标签,首先在顶部创建一个document标签,然后将token分别压入栈里面,当判断是一个结束标签时,会弹出栈,这样一个完整的标签就得到了,依次这样就得到了整个DOM树 解析算法

html预解析

当html中有css和JavaScript时,可能这些文件会修改dom所以,html会等这些文件加载完成后再继续执行,这就造成了阻塞DOM,所以chrome增加了一个预解析操作,当渲染引擎收到字节流后,回开启一个预解析线程,扫描分析html文件是否包含JavaScript和css文件,当有时会提前下载这些文件。 优化:在js中使用async和defer,使用async标志的脚本文件一旦加载完成,会立即执行;而使用了defer标记的脚本文件,需要等到DOMContentLoaded事件之后执行。

构建CSSOM树

来源

  1. link引用的css文件
  2. style 内的css
  3. 元素内嵌的css

作用

  1. 提供给JavaScript操作样式表的能力
  2. 为布局树的合成提供基础样式信息

CSSOM树

标准化css

讲颜色值、bold、以及em等等转换成标准值

  • 颜色值转换成rbg的格式
  • bold 转换成具体的数值500
  • em等不是标准px转换成标准px

计算出DOM具体每一个节点的具体样式

通过层叠、继承等等方式生成某个节点的具体样式如下图 image.png

创建渲染树

  • 等DOM树和CSSOM树构建完成后就可以进行布局树的构建了
  • 布局树只存在显示的dom结构,以及响应的样式,一些如head标签、display:none元素隐藏的等等都不存在
  • 样式计算复制好基本的布局树结构之后,渲染引擎会为对应的DOM元素选择对应的样式信息
  • 重排(回流) 渲染引擎会进行计算每个元素对应的几何位置和大小

布局

  • 当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”(Layout)。
  • 根据渲染树以及回流得到的几何信息,得到节点的绝对像素称为重绘(Painting)
  • 布局流程的输出是一个“盒模型”,它会精确地捕获每个元素在视口内的确切位置和尺寸,所有相对测量值都将转换为屏幕上的绝对像素。
  • 计算完毕后,相应的布局信息会被重新写到渲染树上,这就形成了布局渲染树。 布局完成后,浏览器会立即发出“Paint Setup”和“Paint”事件,将渲染树转换成屏幕上的像素。

页面绘制

分层

由于页面十分的复杂,有的页面会有一些复杂的动画效果,如果没有分层机制,从渲染树开始重新生成一张图片的话,每次小小的变化就会引起重排、重绘的操作,严重影响页面的渲染效率。所以引入了分层机制,将不同结构生成不同的图片。像一些3D变化、页面滚动、使用z-index进行z轴排序时,浏览器会生成单独的图层。分层信息可以在浏览器开发者工具layers标签中查看。

为了确定哪些元素需要放置在哪一层,主线程需要遍历渲染树来创建一棵层次树(Layer Tree)(在DevTools中这一部分工作叫做“Update Layer Tree”)。如果页面的某些部分应该被放置在一个单独的层上面(滑动菜单)可是却没有的话,你可以通过使用will-change CSS属性来告诉浏览器对其分层。拥有层叠上下文属性的元素会被提升为单独的一层, 需要裁剪(clip)的地方也会被创建为图层。

这种分层属性不可以乱用,因为分层超过一定数量后,分层的合成操作比每个帧中光栅化页面的一小部分还要慢。

浏览器分层处理也是一种优化,不然一点小小的动画就需要重新进行渲染树的修改等等重新走一遍流程。

绘制图层

浏览器在完成分层操作后,就需要对每个图层进行绘制了,在绘制时浏览器并不是直接将每个图层绘制成一张张图片的,首先会将一个个图层绘制分成很多的绘制指令,然后这些指令按照一定的顺序组成待绘制列表,在layer中可以查看。

其实浏览器在绘制阶段就是生成很多的绘制指令,这些是在主线程中执行的,当这些指令生成完后,就需要合成线程来完成响应的绘制操作了。

注意:合成操作是在合成线程完成的,不会影响主线程的执行,所以一些css动画会十分流畅。

合成

很多情况下,由于页面很大,图层也会很大,合成线程不会立即把整个图层都绘制出来,二十把图层氛围很多的图块,这些图块的大小通常是256X256或者512X512的。另外,合成线程会首先合成可视区域的图块为位图。

光栅化 将文档结构、元素的样式、元素的集合信息以及它们的绘制顺序等页面信息转化成显示器的像素的过程。实际上,生成位图的操作是在光栅化阶段来执行的。光栅化就是按照绘制列表中的指令生成图片。

当所有的图块都被光栅化之后,合成线程就会生成一个绘制图块的命令,浏览器相关进程收到这个指令之后,就会将其页面内容绘制在内存中,最后将内存显示在屏幕上,这样就完成了页面的绘制。

GPU加速

现代浏览器都支持GPU,通常会使用GPU加速栅格化的过程,使用GPU生成位图的过程叫快速栅格化,生成的位图也会保存在GPU内存里面。这是一个跨进程、多进程协作的过程,首先渲染进程把生成图块的指令发送给GPU,然后在GPU中生成位图并保存在GPU内存中。

显示

在所有图块完成栅格化后,合成线程会生成一个绘制图块的命令----DrawQuad并提交给浏览器进程。浏览器进程中有一个viz 的组件,用了接受合成线程发过来的DrawQuad命令,然后根据该命令,将内容绘制到内存中,最后将内存数据显示在屏幕上。

到这里,经过这一系列的阶段,编写好的HTML、CSS、JavaScript等文件,经过浏览器就会显示出漂亮的页面了。

图层绘制

刷新频率

60HZ就是每秒更新60张图片,更新的图片来自显卡中一个前缓冲区的地方,显示器就是每秒固定读取60次前缓冲区的图像。 显卡会把图像保存到后缓冲区,一单显卡把合成的图像写好后,系统会把前后缓冲区调换互换。

显示

一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。

浏览器进程里面有一个叫viz的组件,用来接收合成线程发过来的DrawQuad命令,然后根据DrawQuad命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。

到这里,经过这一系列的阶段,编写好的HTML、CSS、JavaScript等文件,经过浏览器就会显示出漂亮的页面了。

DOM树阻塞

JavaScript、css阻塞DOM树生成

在生成dom树的过程中,碰到script标签回进行JavaScript的下载,DOM树的生成会等待JavaScript的下载,因为不可保证js文件中是否有修改dom的操作。 在碰到link标签也会先下载css文件,css是不会阻塞DOM树的构建的,但是在合成布局树的时候需要DOM和CSSOM都加载完成,所以会影响布局树的合成。 为此,浏览器新增了预解析操作,提前查找script标签,提前下载js文件

JS文件不只是阻塞DOM的构建,它会导致CSSOM也阻塞DOM的构建

因为js可以修改样式可以更改CSSOM,不完整的CSSOM是无法使用的,所以js想修改样式需要等到CSSOM生成完成,如果js加载完成后要执行的时候CSSOM还没有创建完成,浏览器就会延迟脚本的执行和DOM的构建,直到CSSOM构建完成后再进行js的执行,最后进行DOM的构建,这就导致了DOM的阻塞。

如何避免阻塞DOM树

  1. 将js文件放到body底部
  2. 使用async和defer来加载js文件
  3. 内联JavaScript、内联css减少js、css文件的加载时间。
  4. 减少文件大小,移除文件中不必要的注释等内容,压缩JavaScript文件体积
  5. 通过媒体查询属性,拆分css为不同的文件,这样只有特定场景才会下载。

回流重排

当我们的操作引发了DOM树中几何尺寸的变化(改变元素的大小、位置、布局方式等),这时渲染树里面改动的节点和它受影响的节点都需要重新计算。在改动发生时,是需要重新经历页面渲染的整个流程的,所以开销很大。

造成重排的操作

  • 页面首次渲染
  • 浏览器窗口大小的变化
  • 元素的内容发生变化
  • 元素的尺寸或者位置发生变化
  • 元素的字体大小发生变化
  • 激活CSS伪类
  • 查询某些属性或者调用某些方法
  • 添加或者删除可见的DOM元素

影响范围

全局范围:从根节点开始,对整个渲染树进行重新布局 局部范围:对渲染树的某个部分或者一个渲染对象进行重新布局

重绘

对DOM的修改导致了样式的变化,但未影响其几何属性(如:颜色、背景色)时,浏览器不需要重新计算元素的几何属性、直接为该元素绘制新的样式。重绘是由对元素绘制属性的修改引发的。

引发重绘的原因

color、background 相关属性:background-color、background-image 等; outline 相关属性:outline-color、outline-width 、text-decoration; border-radius、visibility、box-shadow

减少重排、重绘制

  • 尽量使用css3动画,可以调用GPU加速,在合成阶段速度快且不影响主线程。
  • 不要频繁的操作样式的修改,尽量使用class或者一次修改完成尽量多次操作只让浏览器更新一次。
  • 可以先隐藏元素(display: none),隐藏的元素不影响回流和重绘,操作完再显示,这样只会触发一次更新。