你开了一个视频,又开了10个网页,再开了20个标签页...Chrome 居然没崩?而其他软件早就卡死了。Chrome是怎么做到的?
今天用**"酒店"**的故事,聊聊 Chrome 的多进程架构。
原文地址
进程与线程:有什么区别?
想象一下:
进程如同一个独立的厨房,有自己的灶台、冰箱、厨师。
线程如同厨房里的厨师,多个厨师共享同一个厨房的资源——灶台是共用的,冰箱是共用的,但每个厨师可以同时干活。
进程A(独立厨房) 进程B(独立厨房)
┌─────────────────┐ ┌─────────────────┐
│ 厨师A1 │ │ 厨师B1 │
│ 厨师A2 │ │ 厨师B2 │
│ 厨师A3 │ │ 厨师B3 │
│ ↓ │ │ ↓ │
│ 一个厨师中毒 │ │ 其他厨师正常 │
│ 其他厨师没事 │ │ 继续做饭 │
└─────────────────┘ └─────────────────┘
关键区别:
- 进程是"隔离的":进程A崩溃了,进程B完全不受影响
- 线程共享资源:线程A1崩溃,可能影响整个进程A,其他线程都完蛋
Chrome多进程架构
Chrome 不像某些浏览器把所有功能塞进一个进程,而是把不同任务交给不同进程:
Chrome 多进程架构:
┌─────────────────────────────────────────────────────┐
│ 浏览器主进程(Browser) │
│ (负责UI、地址栏、书签、下载、标签页管理) │
└─────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│渲染进程1 │ │渲染进程2 │ ... │渲染进程N │
│(Tab 1) │ │(Tab 2) │ │(Tab N) │
└─────────┘ └─────────┘ └─────────┘
│ │ │
▼ ▼ ▼
GPU进程 网络进程 插件进程
| 进程 | 职责 | 崩溃影响 |
|---|---|---|
| 浏览器主进程(Browser) | 标签页管理、地址栏、书签、下载、UI渲染 | 整个浏览器崩溃 |
| 渲染进程(Renderer) | 运行网页内容(HTML/CSS/JS) | 只影响当前标签页 |
| GPU进程 | 图形渲染、视频解码、GPU加速 | 不影响网页渲染 |
| 网络进程(Network) | 网络请求、DNS缓存、SSL验证 | 所有标签页断网 |
| 插件进程(Plugin) | 运行浏览器插件(如Flash、PDF插件) | 只影响使用该插件的页面 |
| 实用工具进程(Utility) | 处理PDF阅读、扩展安装、打印等 | 不影响主功能 |
渲染进程:每个标签页一个
最重要的进程是渲染进程——每个标签页都有自己的渲染进程:
标签页1 → 渲染进程A(独立内存空间)
标签页2 → 渲染进程B(独立内存空间)
标签页3 → 渲染进程C(独立内存空间)
...
标签页100 → 渲染进程100(独立内存空间)
这就是为什么一个标签页崩溃不会影响其他标签页——每个渲染进程都有自己独立的内存空间,互不干扰。
为什么Chrome选择多进程?
早期浏览器(如IE、Firefox早期版本)都是单进程架构:
单进程浏览器:
┌─────────────────────────────┐
│ 所有标签页 + UI + 插件 + JS │ ← 全在一个进程
│ 一个崩,全部崩 │
└─────────────────────────────┘
单进程的问题:
- 一个标签页死循环,UI就卡死
- 一个标签页内存泄漏,慢慢拖垮整个浏览器
- 插件崩溃,浏览器跟着崩溃
- JS可以访问浏览器内部任意资源,安全隐患大
Chrome设计者认为:稳定性和安全性比内存占用更重要。
进程间通信:IPC
不同进程之间怎么"对话"?
Chrome 使用**IPC(Inter-Process Communication,进程间通信)**机制。就像酒店房间之间不能直接串门,得通过对讲机沟通。
渲染进程(标签页1) 浏览器主进程
┌──────────────────┐ ┌──────────────────┐
│ JS执行引擎 │ │ 标签页管理器 │
│ HTML解析器 │ ←───────→│ UI渲染引擎 │
│ CSS解析器 │ IPC │ 地址栏管理 │
│ DOM操作 │ 消息通道 │ 书签管理 │
└──────────────────┘ └──────────────────┘
IPC消息类型
Chrome中主要的消息类型:
| 消息类型 | 说明 | 示例 |
|---|---|---|
| ViewMsg | 渲染进程→主进程 | "用户点击了链接" |
| HandleViewMsg | 主进程→渲染进程 | "创建新标签页" |
| Route | 路由消息 | 跨进程路由分发 |
IPC工作流程
点击链接时,Chrome 内部经历了:
┌───────────────────────────────────┐
│ 步骤1:渲染进程检测点击 │
│ JS事件监听器捕获 <a> 点击 │
└───────────────────────────────────┘
│
▼ ViewMsg_LinkOpened
│
┌───────────────────────────────────┐
│ 步骤2:主进程接收消息 │
│ 决定打开新标签页 │
└───────────────────────────────────┘
│
▼ HandleViewMsg_CreateWidget
│
┌───────────────────────────────────┐
│ 步骤3:创建新渲染进程 │
│ 分配新内存空间,初始化V8引擎 │
└───────────────────────────────────┘
│
▼ Channel_LoadURL
│
┌───────────────────────────────────┐
│ 步骤4:新渲染进程加载URL │
│ 网络请求、HTML解析、渲染 │
└───────────────────────────────────┘
整个过程仅需几十毫秒。
渲染进程内部:线程
每个渲染进程内部也不是单线程,而是多线程协作:
渲染进程内部:
┌───────────────────────────────────────┐
│ 主线程(Main Thread) │
│ • V8 JS引擎执行 │
│ • HTML/CSS解析 │
│ • DOM树构建·布局计算·事件处理 │
│ • requestAnimationFrame │
└───────────────────────────────────────┘
│
┌───────────┴───────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ 合成线程 │ │ 光栅线程 │
│(Compositor)│ │ (Raster) │
├──────────────┤ ├──────────────┤
│• 图层合成 │ │• 绘制指令执行 │
│• 滚动·动画 │ │• 像素填充 │
│• 接收输入事件│ │• 纹理上传GPU │
└──────────────┘ └──────────────┘
| 线程 | 职责 | 为什么需要独立 |
|---|---|---|
| 主线程 | JS执行、DOM、Layout、事件处理 | JS必须单线程执行 |
| 合成线程 | 图层合成、滚动、动画 | 滚动必须60fps,不能等JS |
| 光栅线程 | 绘制指令执行、像素填充 | 耗时操作,不能阻塞主线程 |
为什么主线程这么忙?
主线程要干太多事情:
- JS引擎执行
- HTML解析成DOM树
- CSS解析成CSSOM
- DOM + CSSOM = 渲染树
- 布局计算每个元素位置
- 绘制指令生成
- 事件处理
- 定时器回调
- 网络回调
- ...
这就是为什么长任务(Long Task)会卡页面——主线程太忙,用户的点击、滚动都没人处理。
合成线程的秘密
Chrome把滚动交给了合成线程处理,不经过主线程:
传统方式(经过主线程):
滚动事件 → 主线程处理 → 重新布局 → 重绘 → 合成
↑
可能被JS阻塞
Chrome方式(合成线程直接处理):
滚动事件 → 合成线程 → 直接合成 → 输出
↑
完全不经过主线程
所以即使JS卡住了,页面滚动和动画依然流畅。
安全机制:沙箱
渲染进程为什么能"安全"地运行任意网页?
因为 Chrome 给渲染进程加了沙箱(Sandbox)——如同酒店房间:你可以用自己的东西,但不能动酒店的基础设施,也不能进别人房间。
沙箱限制:
渲染进程能做的事:
├── ✅ 执行JS(V8引擎隔离)
├── ✅ 操作DOM(沙箱内DOM树)
├── ✅ 计算样式
└── ✅ 发送网络请求(通过IPC代理)
渲染进程不能做的事:
├── ❌ 直接读写文件系统
├── ❌ 直接访问摄像头/麦克风(需用户授权)
├── ❌ 直接访问系统剪贴板(全权)
├── ❌ 直接读取本机Cookie/密码
├── ❌ 直接创建网络连接(必须经过网络进程)
└── ❌ 直接调用系统API
沙箱的技术原理
沙箱主要依赖操作系统提供的隔离机制:
| 机制 | 说明 |
|---|---|
| 进程隔离 | 每个渲染进程有独立虚拟地址空间 |
| 用户权限限制 | 渲染进程以低权限用户运行 |
| 系统调用过滤 | 禁止某些危险系统调用 |
| 文件访问限制 | 无法访问用户文件 |
即使网页中的恶意代码能执行,它也被"关在笼子里",无法直接伤害你的电脑。
Site Isolation:更严格的安全
2018 年 Chrome 引入Site Isolation(站点隔离),把安全提升到新级别。
以前的规则
每个标签页一个渲染进程:
标签页1 → 渲染进程A → 可以访问标签页1的内存
标签页2 → 渲染进程A → 可以访问标签页2的内存
↓
同一个进程
理论上可以访问彼此
现在的规则
每个跨站点的iframe也可能是独立进程:
example.com 页面:
┌─────────────────────────────────────────┐
│ 主页面(主框架) → 渲染进程A │
│ ├── iframe(ads.example.com) → 渲染进程B │
│ ├── iframe(analytics.com) → 渲染进程C │
│ └── iframe(cdn.example.com) → 渲染进程D │
└─────────────────────────────────────────┘
↓
进程级别完全隔离
为什么需要这么严格?
防止Spectre/Meltdown等侧信道攻击:
攻击场景:
1. evil.com 运行在 渲染进程A
2. victim.com 也在 渲染进程A(作为iframe)
3. 恶意JS利用Spectre漏洞
4. 通过侧信道 timing攻击 读取渲染进程A的内存
5. 理论上可以读到 victim.com 的数据!
有了 Site Isolation,即使 evil.com 被攻破,它的渲染进程也无法访问 victim.com 的数据——因为它们根本不在同一个进程里。
Site Isolation的代价
更严格的隔离带来更高的内存占用:
| 情况 | 进程数 |
|---|---|
| 10个同源标签页 | 10个渲染进程 |
| 10个跨源标签页 | 可能10+个渲染进程 |
| 一个页面有5个跨站iframe | 6个渲染进程 |
Chrome为了安全,愿意付出更多内存代价。
为什么Chrome占用内存高?
很多人抱怨Chrome"吃内存"。
确实,多进程架构比单进程消耗更多内存,但这是故意的设计权衡:
| 对比 | 单进程浏览器 | Chrome多进程 |
|---|---|---|
内存占用 | 低 | 高(每个进程有独立内存空间) |
稳定性 | 一个标签页崩,全部崩 | 一个崩,不影响其他 |
安全性 | 低(JS可以访问更多资源) | 高(沙箱保护,进程隔离) |
流畅度 | JS卡住就卡顿 | 滚动动画由合成线程处理,更流畅 |
溃恢复 | 全部丢失 | 崩溃的标签页可以单独恢复 |
Chrome的内存管理优化
虽然多进程更耗内存,但Chrome也做了很多优化:
- 渲染进程合并:同源的多个标签页可能共享一个渲染进程
- 内存共享:使用**共享内存(Shared Memory)**减少复制
- 进程休眠:长时间未激活的标签页进程可以休眠
- 垃圾回收优化:V8 的垃圾回收已经高度优化
什么时候会内存爆炸?
内存爆炸场景:
├── 开100个淘宝/京东商品页(每个都有大量JS)
├── 开50个在线文档(Google Docs、Notion)
├── 开20个视频网站(爱奇艺、优酷、B站)
└── 结果:内存占用轻松上10GB
这是Chrome的"有钱任性"设计哲学——用内存换稳定性和用户体验。
总结:Chrome核心知识点
| 概念 | 说明 | 类比 |
|---|---|---|
| 多进程架构 | 不同任务交给不同进程 | 酒店各部门分工 |
| 渲染进程 | 每个标签页一个,隔离运行 | 每人一间房 |
| IPC通信 | 进程间通过消息传递协作 | 对讲机沟通 |
| 主线程 | JS执行、DOM、Layout、事件处理 | 客房服务员(单线程) |
| 合成线程 | 滚动、动画(不经主线程) | 专属电梯(直达) |
| 沙箱 | 限制渲染进程权限 | 房间门禁 |
| Site Isolation | 跨站iframe也隔离 | 同一房间的不同访客也分开 |
| 内存换稳定 | 多进程占用更多内存,但更安全稳定 | 酒店房间多,但互不干扰 |
核心思想:Chrome用"酒店"架构——每个房间(进程)独立,隔音好,一个房间出问题不影响其他;房间内有限制,不能动基础设施;甚至同一页面的不同访客也要隔开。
技术不复杂,但正是这套架构,让"100个网页同时运行"成为可能。
下次 Chrome 占用几百MB甚至几GB内存时,别急着骂它——那是它"有钱任性"的设计,是为了让你的浏览器更稳定、更安全、更流畅。