面灵AI→

360 C++ 二面:多进程浏览器架构、Mojo IPC 与 V8 对象绑定

轮次
二面
时间
2026-10
来源
牛客网

《面试题目》

  1. 你们浏览器为什么坚持一个标签页一个渲染进程,这个成本算过吗?
  2. 进程间通信为什么最后选 Mojo,而不是共享内存加管道自己拼?
  3. CEF 嵌到客户端里,你们踩过最深的坑是什么?
  4. V8 的 isolate 和 context 到底是什么关系?多页面怎么做隔离?
  5. C++ 对象暴露给 JS,怎么保证它不被 GC 提前收走?
  6. 一个 JS 对象能活多久你控制得住吗?GC 交互出过什么问题?
  7. 沙箱在软件侧到底靠什么兜底,光靠内核够吗?

《参考解析》

一标签一进程的收益与成本:收益是故障隔离与安全边界。渲染进程崩了或 OOM,死的只是一个标签页而不是整个窗口;更重要的是渲染进程能跑在低权限沙箱里,解网页渲染这类不可信代码的漏洞时,攻击者只拿到一个降权进程。成本是每个进程都有独立地址空间、独立 V8 堆、连接与句柄,常驻内存随标签数线性上涨。所以不会无限拆:同站点复用进程、后台标签冻结(停掉定时器与渲染)、长时间不看就 discard(丢渲染进程只留导航状态,切回来时重放)、给进程数设上限。能补一句「什么情况下反而该合并」——同源 iframe、低内存设备——比只背隔离收益完整。

为什么自研 IPC 不划算:共享内存吞吐最好,大块数据零拷贝,但生命周期、同步、崩溃后回收、跨版本兼容、权限校验全要自己实现,漏一处就是泄漏或越权。成熟框架给的是 message pipe 抽象、接口版本化、句柄跨进程传递、路由与权限,代价是封装开销。实际分路:高频大数据走共享内存,低频控制消息走 message pipe。踩坑很典型:自研的共享内存段在进程崩溃后没人回收,句柄越攒越多,最后交给 broker 进程托管加引用计数。

CEF 集成的三类坑:版本绑定,CEF 必须与 Chromium 分支严格对齐,升级常带来体积膨胀与 API 变更,安装包要能裁掉用不到的组件。线程模型,CefBrowser 的多数调用必须在 UI 线程,工作线程直接调 executeJavaScript 是崩溃源,改成任务投递回 UI 线程再回调。退出顺序,先销毁浏览器再 shutdown,顺序反了会崩在 message loop 上,退出慢通常是子进程没关干净。

V8 的 isolate 与 context:isolate 是一套独立的堆、GC 和执行状态,context 是 isolate 内的全局对象作用域;同 isolate 的多个 context 共享堆和 GC,但 global 各自独立。隔离粒度决定成本:一页一个 isolate 隔离最好,每个 isolate 有固定内存底噪;同源 iframe 共享 isolate 省内存,跨站必须分开,这就是站点隔离。事故形态是:所有页面共用一个 isolate,某个页面把堆撑爆触发全堆 GC,其他页面一起卡住。

C++ 对象与 JS 的生命周期:常规做法是 wrapper 加 internal field 存裸指针,配弱回调在 JS 对象回收时析构 C++ 对象。反方向要小心:C++ 长期持 JS 对象必须用 Persistent,不用时 Reset,漏一处就是稳定泄漏;更隐蔽的是循环引用——C++ 持 Persistent、JS 又通过 wrapper 指回来,V8 的 GC 看不到 C++ 这条边,只能显式解绑或改用弱引用。

GC 交互与外部内存:V8 分代回收,新生代 Scavenge、老生代标记整理,活过一次就晋升。把大对象挂到全局等于直接送进老生代,Major GC 停顿明显。观测用 —trace-gc 看频率与停顿,配堆快照定位分配热点,比如每帧创建大量临时 TypedArray,改成对象池后 GC 次数降一个量级。容易忽略的是外部内存:C++ 持有的内存不计入 V8 堆,V8 以为宽裕就不着急回收,RSS 一路涨,必须用 AdjustAmountOfExternalAllocatedMemory 上报。

沙箱靠什么兜底:光靠内核不够,要降权、收窄系统调用面、双层限制。渲染进程启动即丢 token,只保留低完整性级别,写不了用户目录也读不了别的进程,再用 restricted token 与 job object 限制资源;文件、注册表、网络等敏感操作由 broker 进程代理并按策略校验;内核侧再叠作业对象、AppContainer 之类机制。设计前提是假设渲染进程已被攻陷。