<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Zygote on zed的博客</title><link>https://www.dust-zed.site/tags/zygote/</link><description>Recent content in Zygote on zed的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Mon, 09 Feb 2026 10:10:12 +0800</lastBuildDate><atom:link href="https://www.dust-zed.site/tags/zygote/index.xml" rel="self" type="application/rss+xml"/><item><title>Android App 启动流程：Launcher、AMS、Zygote 与 ActivityThread</title><link>https://www.dust-zed.site/android/android-app-startup/</link><pubDate>Mon, 09 Feb 2026 10:10:12 +0800</pubDate><guid>https://www.dust-zed.site/android/android-app-startup/</guid><description>&lt;h2 id="从点击图标到页面显示">从点击图标到页面显示
&lt;/h2>&lt;p>App 冷启动可以拆成三个关键角色：Launcher 发起请求，System Server 中的 AMS 负责调度，Zygote 负责 fork 新进程。&lt;/p>
&lt;h2 id="核心结论">核心结论
&lt;/h2>&lt;ol>
&lt;li>Launcher 不能直接创建目标 App 进程，它通过 Binder 请求 AMS 启动 Activity。&lt;/li>
&lt;li>AMS 如果发现目标进程不存在，会通过 Socket 请求 Zygote fork 新进程。&lt;/li>
&lt;li>Zygote 使用 fork 和 Copy-on-Write 复用预加载类与资源。&lt;/li>
&lt;li>新进程入口是 &lt;code>ActivityThread.main()&lt;/code>，这里会启动主线程 Looper。&lt;/li>
&lt;li>AMS 通过 &lt;code>IApplicationThread&lt;/code> 通知新进程绑定 Application 并启动 Activity。&lt;/li>
&lt;/ol>
&lt;h3 id="第一阶段请求与孵化the-request--the-fork">第一阶段：请求与孵化（The Request &amp;amp; The Fork）
&lt;/h3>&lt;p>Launcher 自己不能创建目标 App 进程，它必须把启动请求交给 AMS。&lt;/p>
&lt;h4 id="1-launcher---ams-binder-通信">1. Launcher -&amp;gt; AMS (Binder 通信)
&lt;/h4>&lt;ul>
&lt;li>动作： 当你点击桌面图标，Launcher 进程调用&lt;code>startActivity&lt;/code>。&lt;/li>
&lt;li>底层： Launcher(Client)通过 Binder 告诉 AMS（Server）：“我要启动某某 App 的 MainActivity”。&lt;/li>
&lt;li>知识点：这里发生了一次 Binder IPC。&lt;/li>
&lt;/ul>
&lt;h4 id="2-ams的决策">2. AMS的决策
&lt;/h4>&lt;ul>
&lt;li>AMS 检查这个 App 的进程是否已经存在。&lt;/li>
&lt;li>如果存在（热启动）：直接通过 Binder 通知该进程&lt;code>onResume&lt;/code>,速度很快。&lt;/li>
&lt;li>如果不存在（冷启动）：AMS 必须创建一个新进程。&lt;/li>
&lt;/ul>
&lt;h4 id="3-ams---zygotesocket通信--高频">3. AMS -&amp;gt; Zygote（Socket通信 &amp;ndash; 高频）
&lt;/h4>&lt;ul>
&lt;li>动作：AMS 发现需要新进程，它不是自己去创建，而是通知 &lt;strong>Zygote 进程&lt;/strong>。&lt;/li>
&lt;li>底层：AMS 通过&lt;code>LocalSocket&lt;/code>发送请求给 Zygote。&lt;/li>
&lt;li>必须知道：为什么这里不用 &lt;strong>Binder&lt;/strong> 而用 &lt;strong>Socket&lt;/strong>？
&lt;ul>
&lt;li>&lt;strong>死锁风险&lt;/strong>：Zygote 的主要工作是&lt;code>fork()&lt;/code>。在 Linux 中，&lt;code>fork()&lt;/code>只会拷贝当前&lt;strong>线程&lt;/strong>，而不会拷贝&lt;strong>其他线程&lt;/strong>。如果 Zygote 使用 Binder（它是多线程的），且&lt;code>fork()&lt;/code>时某个 Binder 锁被其他线程持有，新进程里这个锁就永远处于&lt;strong>被锁住&lt;/strong>的 状态（不会被释放？因为持有线程的锁没有被拷贝过来），导致新进程一启动就&lt;strong>死锁&lt;/strong>。&lt;/li>
&lt;li>&lt;strong>Socket 优势&lt;/strong>：Socket 是单线程、顺序执行的，对于&lt;code>fork&lt;/code>这种敏感操作更安全。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h4 id="4-zygote---新进程fork">4. Zygote -&amp;gt; 新进程（Fork）
&lt;/h4>&lt;ul>
&lt;li>动作：Zygote 收到请求，执行&lt;code>fork()&lt;/code>系统调用，复制自身，生成一个新的 App 进程。&lt;/li>
&lt;li>底层：利用**Copy-on-Write(写时复制)**技术。新进程共享 Zygote 的资源库（系统类、资源），只有当新进程需要修改数据时，才会真正拷贝物理内存。这就是 Android 应用启动快，省内存的秘诀。&lt;/li>
&lt;/ul>
&lt;h3 id="第二阶段初始化与绑定init--attach">第二阶段：初始化与绑定（Init &amp;amp; Attach）
&lt;/h3>&lt;p>新进程刚出生时是一张白纸，它不知道自己是谁，也不知道要干嘛。&lt;/p>
&lt;h4 id="5-新进程启动activitythreadmain">5. 新进程启动（ActivityThread.main）
&lt;/h4>&lt;ul>
&lt;li>动作： 新进程开始执行入口函数&lt;code>ActivityThread.main()&lt;/code>。&lt;/li>
&lt;li>底层：这里会初始化我们熟悉的&lt;strong>Looper.prepareMainLooper()&lt;/strong> 和 &lt;strong>Looper.loop()&lt;/strong> &amp;ndash; &lt;strong>主线程的消息循环从此开始运转&lt;/strong>。&lt;/li>
&lt;/ul>
&lt;h4 id="6-新进程--amsbinder-通信">6. 新进程 &amp;ndash; AMS（Binder 通信）
&lt;/h4>&lt;ul>
&lt;li>动作：新进程（现在它是 Client）通过 Binder 告诉 AMS（Server）：“大佬，我启动好了，请把 Application 信息发给我。”&lt;/li>
&lt;li>方法名： &lt;code>attachApplication&lt;/code>。&lt;/li>
&lt;li>知识点：这是新进程主动发起的第一次 Binder 调用。&lt;/li>
&lt;/ul>
&lt;h3 id="第三阶段生命周期回调lifecycle">第三阶段：生命周期回调（LifeCycle）
&lt;/h3>&lt;p>AMS 确认“人”到了，开始派活。&lt;/p>
&lt;h4 id="7-ams---新进程binder通信">7. AMS -&amp;gt; 新进程（Binder通信）
&lt;/h4>&lt;ul>
&lt;li>动作： AMS 通过 Binder 接口（&lt;code>IApplicationThread&lt;/code>）发回指令。&lt;/li>
&lt;li>指令 1：&lt;code>bindApplication&lt;/code> &amp;ndash; 触发&lt;code>Application.onCreate()&lt;/code>。&lt;/li>
&lt;li>指令 2：&lt;code>scheduleLaunchActivity&lt;/code> &amp;ndash; 触发 &lt;code>Activity.onCreate()&lt;/code>。&lt;/li>
&lt;/ul>
&lt;h4 id="8-主线程处理handler">8. 主线程处理（Handler）
&lt;/h4>&lt;ul>
&lt;li>动作: 新进程的 Binder 线程池收到 AMS 指令后，通过&lt;code>Handler&lt;/code>把消息抛给主线程(&lt;code>H&lt;/code>类)。&lt;/li>
&lt;li>执行： 主线程 Looper 收到消息后，执行&lt;code>handleBindApplication&lt;/code>和&lt;code>handleLaunchActivity&lt;/code>。&lt;/li>
&lt;li>结果：在屏幕上终于显示出了界面。&lt;/li>
&lt;/ul>
&lt;h3 id="核心图谱android-进程启动三部曲">核心图谱：Android 进程启动三部曲
&lt;/h3>&lt;p>我们要搞清楚三个角色的出场顺序：Init 进程 -&amp;gt; Zygote进程 -&amp;gt; System Server 进程。&lt;/p>
&lt;h4 id="第一阶段init-进程pid--1">第一阶段：Init 进程（PID = 1）
&lt;/h4>&lt;ul>
&lt;li>角色： Linux 系统的第一个用户级进程，所有进程的老祖宗。&lt;/li>
&lt;li>动作：
&lt;ol>
&lt;li>电源键按下，Bootloader 引导 kernel 启动。&lt;/li>
&lt;li>Kernel 启动完毕后，查找并执行&lt;code>/init&lt;/code>文件。&lt;/li>
&lt;li>Init 进程读取&lt;code>init.rc&lt;/code>配置文件。&lt;/li>
&lt;li>关键指令： 在配置文件里写着一行&lt;code>service zygote ...&lt;/code>，Init 也就是在这里把 &lt;strong>Zygote&lt;/strong>启动了起来。&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ul>
&lt;h4 id="第二阶段zygote-进程">第二阶段：Zygote 进程
&lt;/h4>&lt;ul>
&lt;li>角色：Android Java 世界的孵化器。所有的 App 进程（包括 System Server）都是它的子孙。&lt;/li>
&lt;li>动作（ZygoteInit.main）:
&lt;ol>
&lt;li>启动虚拟机（ART/Dalvik）：这是 Java 代码运行的基础。&lt;/li>
&lt;li>预加载资源（Preload）：（面试必考）Zygote 会把常用的系统类（&lt;code>String&lt;/code>, &lt;code>Activity&lt;/code>等）和资源（drawable, styles）加载到自己的内存里。
&lt;ul>
&lt;li>为什么要这样做？因为后面 fork 出的新进程会继承这些内存（Copy-on-Write）。大家共享这些资源，既省内存，启动又快。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>启动 System Server：Zygote 会 &lt;code>fork&lt;/code> 出 System Server 进程。&lt;/li>
&lt;li>&lt;strong>建立 Socket 服务端&lt;/strong>: 任务完成，Zygote 建立一个&lt;code>LocalSocket&lt;/code>服务端，进入死循环，等待 System Server 发号施令。&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ul>
&lt;h4 id="第三阶段system-server-进程">第三阶段：System Server 进程
&lt;/h4>&lt;ul>
&lt;li>角色： Android 系统的“CEO”。它虽然是 Zygote 生成的，但它掌管着 Zygote 的接单权。&lt;/li>
&lt;li>动作（SystemServer.main）:
&lt;ol>
&lt;li>启动 Binder 线程池：&lt;strong>（关键差异）&lt;/strong> Zygote 为了防死锁不用 Binder，但 System Server 是大管家，必须处理海量并发请求，所以它第一件事就是初始化 Binder。&lt;/li>
&lt;li>启动系统服务：它一口气启动了 80+ 个服务，包括我们熟悉的：
&lt;ul>
&lt;li>AMS (Activity Manager Service) : 统管四大组件。&lt;/li>
&lt;li>WMS（Window Manager Service）：统管窗口。&lt;/li>
&lt;li>PMS （Package Manager Service）：统管安装包。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>进入 Looper 循环：这里的 Looper 就在等着处理各种 Binder 也就是 App 发来的请求了。&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ul>
&lt;h4 id="夺命连环问">夺命连环问
&lt;/h4>&lt;h5 id="1-为什么system-server-要被zygote-fork出来而不是有init直接启动">1. 为什么System Server 要被Zygote Fork出来，而不是有Init直接启动？
&lt;/h5>&lt;p>这个问题考察对**Copy-on-Write(COW)**的理解。&lt;/p>
&lt;ul>
&lt;li>回答要点：
&lt;ul>
&lt;li>资源共享：System Server 本质上也是一个 Java 进程，它也需要运行环境和系统类。&lt;/li>
&lt;li>继承红利：如果 Init 直接启动，System Server 就得自己重新加载一遍虚拟机和资源，既慢有浪费内存。让 Zygote fork 它，它就能直接继承 Zygote 预加载好的所有资源（类、图片、各种 drawable），落地即满级。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h5 id="2-zygote-和system-server是怎么配合工作的">2. Zygote 和System Server是怎么配合工作的？
&lt;/h5>&lt;p>这个问题考察你对 IPC 架构的理解。&lt;/p>
&lt;ul>
&lt;li>回答要点：
&lt;ul>
&lt;li>父子关系：Zygote fork 了 System Server。&lt;/li>
&lt;li>C/S 关系（Socket）：
&lt;ul>
&lt;li>System Server（里的 AMS）充当 Client。&lt;/li>
&lt;li>Zygote 充当 &lt;strong>Socket Server&lt;/strong>。&lt;/li>
&lt;li>当 AMS 需要启动新 App 时，它通过 Socket 发消息给 Zygote：“给我 fork 个新进程”。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>C/S 关系（Binder）：
&lt;ul>
&lt;li>App 启动后（Client），会通过 Binder 找 AMS 注册自己。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h5 id="3-如果system-server挂了crash会发生什么">3. 如果System Server挂了（Crash）会发生什么？
&lt;/h5>&lt;p>这个问题考察你对 &lt;strong>Android 系统稳定性&lt;/strong>的认知&lt;/p>
&lt;ul>
&lt;li>回答要点：
&lt;ul>
&lt;li>手机重启（Soft Reboot）。&lt;/li>
&lt;li>Zygote 会监听他的子进程。如果 System Server 挂了，Zygote 会&amp;quot;自杀&amp;quot;（因为大管家没了，系统没法运行了）。&lt;/li>
&lt;li>Init 进程发现 Zygote 挂了，会重启 Zygote。&lt;/li>
&lt;li>Zygote 重启后，又会再次启动 System Server。&lt;/li>
&lt;li>这就是为什么有时候手机卡死黑屏后，会显示开机动画重新进入系统。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h5 id="4-为什么zygote不直接当system-server呢">4. 为什么zygote不直接当System Server呢
&lt;/h5>&lt;p>一句话就是 &lt;strong>各司其职&lt;/strong>。具体来说，是因为 &lt;strong>内存洁癖&lt;/strong>、&lt;strong>生死隔离&lt;/strong>、&lt;strong>并发死锁&lt;/strong> 和 &lt;strong>权限安全&lt;/strong>这四大硬性约束。&lt;/p>
&lt;p>我们来逐一拆解：&lt;/p>
&lt;h6 id="1-内存洁癖zygote必须是纯净的母体">1. 内存洁癖：Zygote必须是&amp;quot;纯净的母体&amp;quot;
&lt;/h6>&lt;p>这是最核心的原因。&lt;/p>
&lt;ul>
&lt;li>Zygote的角色： 它是一个&lt;strong>模板(Template)&lt;/strong> 。他的内存里应该只包含所有 &lt;strong>App 公用的、只读的&lt;/strong>基础资源（比如&lt;code>String&lt;/code>类、&lt;code>View&lt;/code>类、&lt;code>drawable&lt;/code>资源）。&lt;/li>
&lt;li>System Server 的角色：它是一个&lt;strong>管理员&lt;/strong>。它的内存里塞满了 &lt;strong>动态的、脏的&lt;/strong>运行时数据（比如当前运行了哪些 App、窗口的位置、电池电量、用户的锁屏密码等）。&lt;/li>
&lt;/ul>
&lt;p>如果 Zygote 直接当 System Server：Zygote 的内存里就会包含上述的脏数据。当 Zygote fork 一个新的 App（比如微信）时，根据&lt;code>Copy-on-Write&lt;/code>机制，&lt;strong>微信会继承 Zygote 的所有内存数据&lt;/strong>。&lt;/p>
&lt;ul>
&lt;li>后果：微信一启动，内存里就莫名其妙多了&amp;quot;当前所有 App 的列表&amp;quot;、“电池电量记录”等它根本不需要、甚至不该知道的数据。这不仅浪费内存，还可能导致数据泄露。&lt;/li>
&lt;/ul>
&lt;p>所以：Zygote 必须保持“纯洁”和“静态”，只包含通用的代码和资源。一旦 fork 出 System Server，System Server 就可以尽情地在自己的进程里搞脏自己的内存，而不会影响后续孵化出来的 App。&lt;/p>
&lt;h6 id="2-生死隔离-zygote不能死">2. 生死隔离： Zygote不能死
&lt;/h6>&lt;ul>
&lt;li>Zygote 的使命：它是 Android 世界的女娲。只要它活着，App 挂了可以重开，System Server 挂了可以重启。&lt;/li>
&lt;li>System Server 的风险：它运行着 80+ 个系统服务，逻辑极其复杂，代码量巨大，还要处理各种第三方 App 的奇怪请求。它崩溃的概率远高于 Zygote。&lt;/li>
&lt;/ul>
&lt;p>如果 Zygote 直接当 System Server：一旦 AMS 或 WMS 因为某个 Bug 崩溃了，整个 Zygote 进程就会挂掉。&lt;/p>
&lt;ul>
&lt;li>后果： Android 系统彻底死透了。因为孵化器没了，没人能重建系统，也没人能孵化新 App，只能等待 Linux 内核（Init 进程）重启手机。&lt;/li>
&lt;/ul>
&lt;p>现在的架构：System Server 挂了 -&amp;gt; Zygote 还在 -&amp;gt; Zygote 监听到子进程死亡 -&amp;gt; Zygote 重启 System Server -&amp;gt; 手机&amp;quot;软重启&amp;quot;（Soft reboot，只闪一下开机动画，不用重新引导内核）。&lt;/p>
&lt;h6 id="3-并发死锁binder与fork的天生互斥">3. 并发死锁：Binder与Fork的天生互斥
&lt;/h6>&lt;p>我们在前面提到过，&lt;strong>Zygote 为什么用 Socket 而不用 Binder&lt;/strong>?因为 Binder 是多线程的，而&lt;code>fork()&lt;/code>在多线程环境下即易死锁。&lt;/p>
&lt;ul>
&lt;li>System Server: 必须高度依赖 Binder（它是系统的大管家，要处理成千上万的并发请求）。所以 System Server 里有密密麻麻的 Binder 线程池。&lt;/li>
&lt;li>Zygote ：必须保持单线程（或者极简的线程模型）、才能安全地执行&lt;code>fork()&lt;/code>。&lt;/li>
&lt;/ul>
&lt;p>如果 Zygote 直接当 System Server：它既要运行 Binder 线程池来处理系统服务，又要执行&lt;code>fork()&lt;/code>来孵化 App。&lt;/p>
&lt;ul>
&lt;li>后果：当 zygote 正在处理一个 Binder 请求（锁住了某个资源）时，突然来了一个孵化请求执行&lt;code>fork()&lt;/code>。新生成的 App 进程里，那个&amp;quot;锁&amp;quot;是被锁住的，但持有锁的线程没被拷贝过来 &amp;ndash; &lt;strong>死锁发生了，新 App 永远卡死在启动界面&lt;/strong>。&lt;/li>
&lt;/ul>
&lt;h6 id="4-权限安全root-vs-system">4. 权限安全：Root vs System
&lt;/h6>&lt;ul>
&lt;li>Zygote: 必须拥有 Root 权限。因为它要操作底层资源，要&lt;code>setuid&lt;/code>(给新 App 分配独立的用户 ID)，这都是特权操作。&lt;/li>
&lt;li>System Server: 只需要 System 权限（&lt;code>uid=1000&lt;/code>）。它虽然权力大，但不能拥有 Root 权限，以防被黑客利用攻破整个系统内核。&lt;/li>
&lt;/ul>
&lt;p>现在的架构：Zygote(Root) -&amp;gt; fork() -&amp;gt; 新进程 -&amp;gt; 降权（Drop Permission） -&amp;gt; 变成了 System Server （System User）或者 App（App User）。这样最安全，权限最小化。&lt;/p>
&lt;h5 id="5-为什么binder-被锁阻塞的线程拷贝过来就永远是锁住的呢">5. 为什么Binder 被锁阻塞的线程拷贝过来就永远是锁住的呢？
&lt;/h5>&lt;p>简单的答案是：因为&lt;code>fork()&lt;/code>只有复制当前执行&lt;code>fork()&lt;/code>的那个线程，而不会复制其他线程。但是，它却复制了所有线程持有锁的状态。&lt;/p>
&lt;p>假设 Zygote 进程里有两个线程：&lt;/p>
&lt;ol>
&lt;li>线程 A（Binder 线程）：正在准备处理一个系统请求，它拿了一把锁（Mutex），准备写日志。&lt;/li>
&lt;li>线程 B（主线程）：准备执行&lt;code>fork()&lt;/code>来孵化新进程（比如启动微信）。&lt;/li>
&lt;/ol>
&lt;h6 id="第一步zygote正常运行">第一步：Zygote正常运行
&lt;/h6>&lt;ul>
&lt;li>线程 A 运行到一半，执行了&lt;code>lock.acquire()&lt;/code>（加锁）。&lt;/li>
&lt;li>此时，锁（Mutex）的状态在内存里变成了&amp;quot;Locked&amp;quot;。&lt;/li>
&lt;li>线程 A 还没来的及释放锁，CPU 时间片到了，切到了线程 B。&lt;/li>
&lt;/ul>
&lt;h6 id="第二步执行fork">第二步：执行Fork
&lt;/h6>&lt;ul>
&lt;li>线程 B 执行 fork&lt;/li>
&lt;li>关键点：Linux 的 fork（）规定，只复制&lt;strong>调用 fork 的那个线程&lt;/strong>（也就是线程 B）。&lt;/li>
&lt;li>后果：新生成的进程（微信进程）里，只有&lt;strong>线程 B 的克隆体，没有线程 A&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h6 id="第三步遗产继承灾难开始">第三步：遗产继承（灾难开始）
&lt;/h6>&lt;ul>
&lt;li>虽然线程 A 没过来，但是 Zygote 进程里的所有内存数据都被复制过来了（通过 Copy-on-Write）。&lt;/li>
&lt;li>重点：那把锁（Mutex）是内存里的一个对象。在 Zygote 里，它是&amp;quot;Locked&amp;quot;状态，所以，在新进程里，这把&lt;strong>锁也依然是 Locked 状态&lt;/strong>。&lt;/li>
&lt;/ul>
&lt;h6 id="第四步新进程启动">第四步：新进程启动
&lt;/h6>&lt;ul>
&lt;li>新进程（微信）开始初始化。&lt;/li>
&lt;li>微信的初始化代码里，也需要写日志（或者使用 Binder）。&lt;/li>
&lt;li>微信的主线程尝试执行&lt;code>lock.accquire()&lt;/code>。&lt;/li>
&lt;/ul>
&lt;h6 id="第五步死锁deadlock">第五步：死锁（DeadLock）
&lt;/h6>&lt;ul>
&lt;li>微信的主线程发现：咦？这把锁已经被锁住了&lt;/li>
&lt;li>于是微信主线程挂起，等待锁被释放&lt;/li>
&lt;li>但是！真正持有这把锁的人 &amp;ndash; &lt;strong>Zygote 里的线程 A&lt;/strong> &amp;ndash; 根本没有被拷贝到微信进程里来&lt;/li>
&lt;li>结局：微信进程流永远没有任何人能去执行&lt;code>lock.release()&lt;/code>。这把锁成了“幽灵锁”，微信进程一出生就卡死在初始化阶段，永远等待一个不存在的线程来解锁。&lt;/li>
&lt;/ul>
&lt;h4 id="什么是copy-on-write">什么是Copy-on-Write
&lt;/h4>&lt;p>这是操作系统偷懒的内存管理策略。&lt;/p>
&lt;p>简单一句话总结：&lt;strong>只有在真正修改数据时，才去复制一份；如果只是读取，那就大家共用一份&lt;/strong>。&lt;/p>
&lt;h5 id="1-核心原理能不复制就不复制">1. 核心原理：能不复制，就不复制
&lt;/h5>&lt;p>为了彻底理解，我们还是用 Zygote 和新 App 的例子。&lt;/p>
&lt;p>假设 Zygote 进程内存里有一张 100MB 的资源地图（里面有 String 类，Drawable 图片，系统主题等）。&lt;/p>
&lt;h6 id="第一阶段fork发生时假装复制">第一阶段：Fork发生时（假装复制）
&lt;/h6>&lt;p>当 Zygote 执行&lt;code>fork()&lt;/code>孵化新App 时：&lt;/p>
&lt;ul>
&lt;li>操作系统不做的事：它不会傻乎乎的把这 100MB 物理内存 Copy 一份给新 App。那太慢了&lt;/li>
&lt;li>操作系统做的事：他只是把 Zygote 的“页表”（Page Table， 也就是内存地址映射关系）复制了一份给新 App。&lt;/li>
&lt;li>结果： Zygote 和新 App 的虚拟地址虽然是独立的，但它们指向的&lt;strong>物理内存是完全同一块&lt;/strong>。&lt;/li>
&lt;li>标记：操作系统会把这块共享的内存区域标记为&lt;strong>只读&lt;/strong>（Read-Only）。&lt;/li>
&lt;/ul>
&lt;h6 id="第二阶段大家只读不写和平共处">第二阶段：大家只读不写（和平共处）
&lt;/h6>&lt;ul>
&lt;li>只要新 App 和 Zygote 都只是读取这些数据（比如读取一个 String，或者加载一张系统图标），它们就一直共用这块物理内存。&lt;/li>
&lt;li>收益：内存占用极低，因为没有产生新的副本。&lt;/li>
&lt;/ul>
&lt;h6 id="第三阶段有人要搞事了触发写时复制">第三阶段：有人要搞事了（触发写时复制）
&lt;/h6>&lt;ul>
&lt;li>动作：突然，新 App 想要修改某个变量（比如把一个静态变量&lt;code>sConfig =&amp;quot;default&amp;quot;&lt;/code> 改为&lt;code>custom&lt;/code>）。&lt;/li>
&lt;li>冲突： 也就是写操作。&lt;/li>
&lt;li>异常：因为这块内存被标记为“只读”，CPU 会立即抛出一个** 缺页异常（Page Fault）**,告诉操作系统有人想要只读数据&lt;/li>
&lt;li>处理：操作系统介入。它会说：好吧，既然你要改，那这页数据就不能共享了。
1. 操作系统把 **这一页（通常是 4KB）**数据复制一份 ，生成一个新的物理内存页。
1. 把新 App 的页表指向这个 新的物理页
1. 把新页的权限改为可读写。&lt;/li>
&lt;li>结果：现在，新 App 可以随意修改这份新的数据了，而 Zygote 手里的那份依然是旧的。它们从此在这一页数据上分道扬镳。&lt;/li>
&lt;/ul></description></item></channel></rss>