初识浏览器 — 你每天用的软件,内部竟然是一个”小型操作系统”
浏览器是前端工程师的”工作台”,但大多数人对它的了解仅停留在”打开网页”这一层。当你真正打开引擎盖,会发现里面运转着一套堪比操作系统的多进程体系。
📋 开篇自测:你已经知道多少?
- 你能说出当前主流浏览器至少使用了哪几个进程吗?
- 进程和线程的区别是什么?为什么浏览器选择多进程而不是多线程?
- Chrome的架构经历了哪些重大变化?未来的方向是什么?
一、浏览器的前世今生
在讨论架构之前,先快速回顾浏览器的演进历程。理解历史,能让你更清楚当下架构选择背后的取舍。
1.1 从单一到多元:浏览器简史
1993 年,NCSA Mosaic 成为第一款被广泛使用的图形化浏览器,它把 HTML 文本和图片渲染到同一个窗口中,让普通人第一次”看到”了互联网。随后 Netscape Navigator 在 1994 年发布,开启了第一轮浏览器商业化浪潮。
1995 年微软推出 Internet Explorer 并将其捆绑在 Windows 中,引发了著名的”浏览器战争”。这场战争的直接后果是:为了争夺市场份额,各厂商疯狂添加私有特性,Web 标准被严重碎片化。
2008 年 Google 发布 Chrome,带来了两项革命性变化:一是多进程架构,二是 V8 JavaScript 引擎。这两项创新从根本上改变了浏览器的设计范式——前者让浏览器告别了”一个标签页崩溃全军覆没”的噩梦,后者则让 JavaScript 的执行速度提升了数十倍。
时间线:浏览器演进里程碑
1993 1995 2004 2008 2017 2020
| | | | | |
Mosaic IE 1.0 Firefox 1.0 Chrome Chrome SOA Edge(Chromium)
| | |
浏览器战争 多进程架构诞生 面向服务架构
1.2 当下格局
截至目前,基于 Chromium 内核的浏览器占据了全球超过 70% 的市场份额。Microsoft Edge、Opera、Brave、以及国内的 360 浏览器、QQ 浏览器等,底层都使用 Chromium。Firefox 使用 Gecko 引擎,Safari 使用 WebKit 引擎。三大引擎形成了三足鼎立的格局,但 Chromium 一家独大的趋势非常明显。
🤔 想一想 浏览器内核大一统对 Web 标准的推进是好事还是坏事?如果只剩一个内核,会带来什么风险?
二、进程与线程:理解浏览器架构的基石
要读懂浏览器的多进程模型,首先需要把”进程”和”线程”这两个概念彻底搞清楚。
2.1 什么是进程
当你双击桌面上的 Chrome 图标时,操作系统做了这样一件事:为 Chrome 分配一块独立的内存空间,加载程序代码,创建至少一个执行线程,然后开始运行。这整个运行中的程序实例,就是一个进程。
关键特征:
- 每个进程拥有独立的内存地址空间
- 进程之间默认无法直接访问对方的数据
- 进程崩溃时,操作系统回收其全部资源,不会波及其他进程
2.2 什么是线程
线程是进程内部的执行单元。一个进程至少包含一个线程(主线程),也可以创建多个线程并行执行任务。
关键特征:
- 同一进程内的多个线程共享内存空间
- 线程之间的通信成本极低(直接读写共享变量)
- 任何一个线程崩溃,整个进程都会被终止
2.3 进程 vs 线程:一张表看清差异
| 维度 | 进程 | 线程 |
|---|---|---|
| 内存空间 | 独立,互相隔离 | 共享所在进程的内存 |
| 创建开销 | 较大(需分配独立地址空间) | 较小(共享进程资源) |
| 通信方式 | IPC(管道、共享内存、消息队列等) | 直接读写共享变量 |
| 崩溃影响 | 仅影响自身 | 导致整个进程崩溃 |
| 安全隔离 | 天然隔离 | 无隔离 |
用一个类比来理解:进程就像一栋栋独立的办公楼,每栋楼有自己的门禁和独立供电;线程就像同一栋楼里的不同楼层,共用电梯和消防通道,但一层着火,整栋楼都要疏散。
进程 vs 线程 示意图
┌────────── 进程 A ──────────┐ ┌────────── 进程 B ──────────┐
│ ┌─────┐ ┌─────┐ ┌─────┐ │ │ ┌─────┐ ┌─────┐ │
│ │线程1│ │线程2│ │线程3│ │ │ │线程1│ │线程2│ │
│ └─────┘ └─────┘ └─────┘ │ │ └─────┘ └─────┘ │
│ 共享内存: 堆、全局变量 │ │ 共享内存: 堆、全局变量 │
│ 独立栈: 每个线程有自己的栈 │ │ 独立栈: 每个线程有自己的栈 │
└────────────────────────────┘ └────────────────────────────┘
↕ IPC 通信 ↕ 互相隔离
🤔 想一想 既然多线程通信成本低、创建开销小,为什么浏览器不采用单进程多线程的方案?回忆一下”崩溃影响”这一行。
三、早期浏览器:单进程的三大痛点
在 2008 年之前,几乎所有浏览器都是单进程架构。以早期的 IE 为例,网络模块、JavaScript 引擎、渲染引擎、插件系统全部运行在同一个进程中。
早期单进程浏览器架构
┌──────────────────────────────────────────┐
│ 单一浏览器进程 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ 网络模块 │ │ 渲染引擎 │ │ JS引擎 │ │
│ └──────────┘ └──────────┘ └────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ 插件(Flash)│ │ 页面管理 │ │ UI控件 │ │
│ └──────────┘ └──────────┘ └────────┘ │
│ │
│ 所有模块共享同一块内存空间 │
└──────────────────────────────────────────┘
这种设计带来了三个严重问题:
3.1 不稳定
Flash 插件是那个年代最常见的崩溃源。由于插件和浏览器运行在同一进程中,一个 Flash 动画的内存越界就能让整个浏览器崩溃——你正在编辑的邮件、正在观看的视频、正在填写的表单,全部丢失。
3.2 不流畅
单进程意味着所有页面的渲染和 JavaScript 执行都在同一个主线程上排队。一个页面中如果有一段执行耗时极长的脚本,其他所有标签页都会被阻塞,用户能做的只有等待,或者强制结束进程。
此外,长期运行的浏览器还面临内存泄漏的累积问题。关闭标签页后,被泄漏的内存无法被回收(因为进程还在运行),时间一长浏览器就会变得越来越慢。
3.3 不安全
插件通常用 C/C++ 编写,可以调用操作系统的底层 API。在单进程架构下,恶意插件可以直接读取浏览器进程中的全部数据——包括其他页面的 Cookie、表单信息、甚至操作系统的文件。没有任何隔离屏障。
四、现代浏览器:多进程架构详解
Chrome 在 2008 年发布时采用了革命性的多进程架构。这一设计的核心思路是:用进程隔离换取稳定性和安全性。
4.1 Chrome 的主要进程
打开 Chrome 的任务管理器(菜单 → 更多工具 → 任务管理器),你会看到远不止一个进程。以下是各进程的职责:
Chrome 多进程架构
┌─────────────────────────────────────────────────────────┐
│ 浏览器主进程 (Browser) │
│ 职责: UI绘制、用户交互、子进程管理、存储、网络调度 │
└──────────┬──────────┬──────────┬──────────┬──────────────┘
│ │ │ │
┌─────▼────┐ ┌───▼────┐ ┌──▼───┐ ┌───▼────────────┐
│ 渲染进程 │ │渲染进程 │ │GPU │ │ 网络进程 │
│ (Tab A) │ │(Tab B) │ │进程 │ │ (Network) │
│ Blink │ │ Blink │ │ │ │ │
│ + V8 │ │ + V8 │ │ │ │ │
│ [沙箱] │ │ [沙箱] │ │ │ │ │
└──────────┘ └────────┘ └──────┘ └────────────────┘
│
┌─────▼──────┐
│ 插件进程 │
│ (Plugin) │
│ [沙箱] │
└────────────┘
浏览器主进程(Browser Process):整个浏览器的”大管家”。负责绘制地址栏、前进/后退按钮、书签栏等 UI 元素,管理所有子进程的生命周期,协调网络请求,处理文件访问和存储。
渲染进程(Renderer Process):每个标签页(或同站点的一组标签页)对应一个独立的渲染进程。Blink 渲染引擎和 V8 JavaScript 引擎都运行在这里。渲染进程被锁在沙箱中,无法直接访问文件系统或网络。
GPU 进程:负责将渲染进程生成的绘制指令转化为屏幕上的像素。3D CSS 变换、视频解码、Canvas 绘制等都依赖 GPU 进程。
网络进程(Network Process):专门负责网络资源的加载。DNS 解析、TCP 连接、HTTP 请求/响应都在这里完成。它最初是浏览器主进程中的一个模块,后来被独立出来以提升稳定性。
插件进程(Plugin Process):为每个使用中的浏览器插件创建独立进程。插件的崩溃不会影响浏览器本身或页面内容。
4.2 为什么”一个标签页一个进程”
这是多进程架构的核心策略之一。当某个标签页的渲染进程因为恶意脚本或内存泄漏而崩溃时,用户看到的只是该标签页显示”喔唷,崩溃了”的提示页面,其他标签页完全不受影响。
同时,当用户关闭一个标签页时,该渲染进程被操作系统完整回收——包括所有被泄漏的内存。这从根本上解决了长时间运行导致的性能衰减问题。
4.3 进程间通信:IPC
既然各进程的内存空间是隔离的,它们之间如何协作?答案是 IPC(Inter-Process Communication,进程间通信)。Chrome 使用 Mojo 作为其 IPC 框架。
IPC 通信示意
渲染进程 浏览器主进程
┌──────────┐ Mojo IPC ┌──────────────┐
│ 需要发起 │ ──────────────> │ 接收请求 │
│ 网络请求 │ │ 转发给网络进程 │
└──────────┘ └──────┬───────┘
│ Mojo IPC
┌─────▼──────┐
│ 网络进程 │
│ 执行HTTP请求│
└────────────┘
例如:渲染进程中的 JavaScript 执行了 fetch('/api/data'),但渲染进程本身不能访问网络(沙箱限制)。于是它通过 IPC 把请求发送给浏览器主进程,浏览器主进程再将请求转发给网络进程,网络进程完成实际的 HTTP 请求后,把响应数据沿原路返回。
4.4 多进程架构的代价
多进程带来了稳定性和安全性的巨大提升,但代价也很明显:
内存占用更高:每个渲染进程都需要独立加载一份 V8 引擎、一份 Blink 渲染引擎的基础代码。虽然同站点的标签页可能共享渲染进程,但打开大量标签页时仍会产生许多渲染进程,每个进程都携带一份基础设施的副本。这就是 Chrome 被称为”内存大户”的根本原因。
架构复杂度更高:进程间的协调、通信、同步都需要精心设计。一个看似简单的用户操作(比如点击链接),背后可能涉及浏览器主进程、网络进程、渲染进程之间的多轮通信。
🤔 想一想 打开 Chrome 的任务管理器,数一下你当前打开了多少个进程。计算一下平均每个进程占用多少内存,思考为什么 Chrome 在 8GB 内存的电脑上经常显得吃力。
五、渲染进程内部的线程分工
虽然浏览器采用了多进程架构,但每个渲染进程内部仍然是多线程的。渲染进程中的线程分工,是理解后续章节(DOM 构建、样式计算、JavaScript 执行、事件循环)的基础。
5.1 主线程(Main Thread)
渲染进程中最核心的线程。它承担了绝大部分工作:
- HTML 解析,构建 DOM 树
- CSS 解析,计算样式
- 布局计算(Layout)
- JavaScript 的解析和执行
- 事件处理(click、scroll、input 等)
正因为主线程身兼数职,JavaScript 的长时间执行才会导致页面卡顿——因为渲染和交互处理都在等它腾出手来。
5.2 合成线程(Compositor Thread)
负责将主线程生成的图层合成为最终的屏幕画面。当你滚动页面或播放 CSS 动画时,合成线程可以独立于主线程工作,这就是为什么 transform 和 opacity 动画比 top/left 动画更流畅的原因。
5.3 光栅化线程池(Raster Threads)
负责将图层中的绘制指令转化为位图(像素数据)。这个过程叫做”光栅化”。Chrome 通常会使用多个光栅化线程并行处理不同的图块。
5.4 其他辅助线程
- IO 线程:处理与其他进程的 IPC 通信
- 定时器线程:管理
setTimeout和setInterval的计时 - 异步网络线程:处理 XMLHttpRequest 和 Fetch 的回调
渲染进程内部线程分工
┌────────────────────────────────────────────────┐
│ 渲染进程 │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ 主线程 (Main) │ │
│ │ HTML解析 → DOM构建 → 样式计算 → 布局 │ │
│ │ → 绘制指令生成 → JS执行 → 事件处理 │ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ▼ 图层数据 │
│ ┌──────────────────────────────────────────┐ │
│ │ 合成线程 (Compositor) │ │
│ │ 图层合成、滚动处理、动画调度 │ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ┌──────────┼──────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────┐┌─────────┐┌─────────┐ │
│ │光栅线程1 ││光栅线程2 ││光栅线程3 │ │
│ │ (Raster)││ (Raster)││ (Raster)│ │
│ └─────────┘└─────────┘└─────────┘ │
│ │
│ ┌─────────┐ ┌──────────┐ ┌───────────┐ │
│ │IO线程 │ │定时器线程 │ │网络回调线程│ │
│ └─────────┘ └──────────┘ └───────────┘ │
└────────────────────────────────────────────────┘
六、Chrome 架构的演进:从多进程到面向服务
Chrome 的架构并不是一成不变的。从 2008 年的初版多进程架构到今天,它一直在演进。
6.1 早期多进程架构(2008—2016)
最初的设计很直接:浏览器主进程 + 若干渲染进程 + 插件进程。网络功能和 GPU 功能都嵌入在浏览器主进程中。这个阶段解决了稳定性和安全性的核心问题,但架构耦合度较高,浏览器主进程变得越来越臃肿。
6.2 独立网络进程与 GPU 进程(2016—2017)
随着 Web 应用复杂度的急剧提升,Chrome 团队开始将功能模块从浏览器主进程中拆分出来。网络模块被独立为网络进程,GPU 相关功能被独立为 GPU 进程。这一阶段的核心目标是”解耦”和”减负”。
6.3 面向服务的架构 / SOA(2017 至今)
Chrome 团队在 2017 年公开提出了 Servicification 计划,将浏览器的各个功能模块逐步重构为独立的”服务”(Service),每个服务可以运行在独立进程中,也可以根据设备资源情况合并到同一个进程中。这是一个持续演进的过程,至今仍在推进。
Chrome 面向服务架构演进
过去(单体主进程):
┌──────────────────────────────┐
│ 浏览器主进程 │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │ UI │ │网络│ │存储│ │设备│ │
│ └────┘ └────┘ └────┘ └────┘ │
└──────────────────────────────┘
现在 / 未来(面向服务):
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│UI服务│ │网络 │ │存储 │ │设备 │ │身份 │
│ │ │服务 │ │服务 │ │服务 │ │服务 │
└──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘
│ │ │ │ │
└────────┴────────┴────────┴────────┘
统一 IPC / Mojo 通信
资源充足的设备: 每个服务独立进程 → 高稳定性
资源受限的设备: 多个服务合并进程 → 低内存占用
这种”弹性架构”的精妙之处在于:在高配电脑上,各服务以独立进程运行,享受最大程度的隔离和稳定性;在低端手机或平板上,多个服务合并到同一个进程中,减少内存开销。Chrome 用同一套代码适配了从桌面到移动端的全部场景。
6.4 站点隔离(Site Isolation)
2018 年,Chrome 67 在桌面端默认启用了站点隔离策略。Android 端于 2019 年的 Chrome 77 跟进,但由于移动设备资源有限,Android 端最初仅对用户登录过的站点(涉及密码输入的站点)启用站点隔离,且要求设备内存 ≥ 2GB,并非像桌面端那样全面隔离——这是安全性与资源消耗之间的一个典型权衡。此前,同一个渲染进程可能同时承载来自不同站点的 iframe。这意味着一个恶意页面有机会通过 Spectre 等侧信道攻击,读取同进程中其他站点的敏感数据。
站点隔离的策略是:不同站点的内容必须运行在不同的渲染进程中。即使页面 A 嵌入了页面 B 的 iframe,这个 iframe 也会被分配到独立的渲染进程。
站点隔离示意
渲染进程 1 (site-a.com) 渲染进程 2 (site-b.com)
┌─────────────────────┐ ┌─────────────────────┐
│ site-a.com 主页面 │ │ site-b.com iframe │
│ │ │ (嵌入在site-a中) │
│ ┌───────────────┐ │ │ │
│ │ iframe占位区域 │──│─────│→ 实际内容在此进程渲染 │
│ └───────────────┘ │ │ │
└─────────────────────┘ └─────────────────────┘
进程边界隔离,无法互相读取内存
这项特性极大地提升了跨站点安全性,但也进一步增加了内存消耗——这就是工程中常见的安全性与性能之间的权衡。
🤔 想一想 站点隔离策略是如何防御 Spectre 漏洞的?如果没有进程隔离,攻击者如何可能窃取其他站点的数据?
七、其他浏览器的架构对比
7.1 Firefox 的 Electrolysis(e10s)
Firefox 的多进程架构项目代号 Electrolysis(e10s),自 2013 年重启开发,2016 年在稳定版(Firefox 48)中开始分阶段启用(最初仅对约 1% 的合格用户默认开启,Firefox 49-52 逐步扩大覆盖范围)。与 Chrome 不同,Firefox 最初只使用有限数量的内容进程(默认 4 个),多个标签页共享同一个内容进程。到后来的 Fission 项目,Firefox 才实现了类似 Chrome 的站点隔离。
7.2 Safari 的 WebKit 架构
Safari 基于 WebKit 引擎。macOS 上的 Safari 同样使用了多进程架构,但其进程模型与 Chrome 有所不同。Safari 的”Web Content”进程对应 Chrome 的渲染进程,但 Safari 在进程复用策略上更加保守——倾向于让多个标签页共享进程以节省内存。
7.3 架构对比一览
| 特性 | Chrome | Firefox | Safari |
|---|---|---|---|
| 内核 | Blink + V8 | Gecko + SpiderMonkey | WebKit + JavaScriptCore |
| 多进程架构 | 2008 年起 | 2016 年起(e10s) | 2010 年代中期 |
| 站点隔离 | 默认开启 | Fission已默认启用 | 部分支持 |
| 进程复用策略 | 较激进(每站点独立) | 中等(有限内容进程) | 较保守(共享进程) |
| GPU进程 | 独立 | 独立 | 与WindowServer集成 |
八、动手实验:观察 Chrome 的进程模型
理论讲完了,现在打开你的 Chrome 浏览器,跟着做几个实验。
8.1 实验一:观察进程数量
- 打开 Chrome,输入
chrome://system查看系统信息 - 按
Shift + Esc(Windows/Linux)或通过菜单打开任务管理器 - 观察当前进程列表,注意区分 Browser、Renderer、GPU、Network 等类型
- 逐个打开新标签页,观察渲染进程数量的变化
8.2 实验二:体验进程隔离
- 打开两个标签页,分别访问不同的网站
- 在其中一个标签页的控制台执行
while(true) {} - 观察:这个标签页会卡死,但另一个标签页仍然可以正常操作
- 在任务管理器中找到卡死的渲染进程,点击”结束进程”
- 被终止的标签页会显示崩溃页面,其他标签页不受影响
8.3 实验三:观察站点隔离
- 创建一个简单的 HTML 文件,嵌入一个跨站 iframe:
<!DOCTYPE html>
<html>
<body>
<h1>主页面 (本地文件)</h1>
<iframe src="https://example.com" width="600" height="400"></iframe>
</body>
</html>
- 在 Chrome 中打开这个文件
- 打开任务管理器,你会发现主页面和 iframe 各自有独立的渲染进程
九、本章知识脉络总结
本章知识地图
浏览器架构
├── 发展历史
│ ├── 单进程时代 (2008之前)
│ │ └── 问题: 不稳定 / 不流畅 / 不安全
│ ├── 多进程时代 (2008-2017)
│ │ └── 解决: 进程隔离 + 沙箱 + IPC
│ └── 面向服务架构 (2017至今)
│ └── 目标: 弹性部署 + 松耦合
│
├── 进程类型
│ ├── 浏览器主进程: UI + 管理
│ ├── 渲染进程: DOM + CSS + JS (沙箱)
│ ├── GPU进程: 图形绘制
│ ├── 网络进程: HTTP请求
│ └── 插件进程: 扩展隔离
│
├── 渲染进程内部线程
│ ├── 主线程: 解析 + 布局 + JS执行
│ ├── 合成线程: 图层合成 + 滚动
│ └── 光栅化线程池: 位图生成
│
└── 安全策略
├── 沙箱机制: 渲染进程无法访问系统资源
└── 站点隔离: 不同站点不同进程
📝 结尾自测:检验你的学习成果
- Chrome 打开一个普通网页至少需要哪 4 个进程?各自的职责是什么?
- 为什么单进程浏览器会出现”一个页面崩溃导致整个浏览器崩溃”的问题?多进程架构如何解决?
- 渲染进程内部的主线程、合成线程、光栅化线程分别负责什么工作?
- Chrome 的面向服务架构(SOA)相比传统多进程架构有什么优势?在资源受限设备上如何适配?
- 站点隔离(Site Isolation)解决了什么安全问题?它的代价是什么?
下一章预告:你已经知道浏览器内部有哪些进程和线程了。下一章我们将跟随一个 URL,从用户按下回车键的那一刻开始,完整追踪”从输入 URL 到页面渲染完成”的全链路流程——DNS 解析、TCP 连接、HTTP 请求、HTML 解析,一个都不漏。
购买课程解锁全部内容
前端进阶第一课:11 章掌握浏览器核心
¥29.90