你的网页Lighthouse跑出了100分,却在用户手里撑不过一个下午。
这不是夸张。实验室里的完美分数,测的是单次加载的干净状态——首屏渲染、交互响应、视觉稳定。它不关心6小时后会发生什么:上百次路由跳转、几千轮轮询请求、React Query缓存里早该清理却残留的数据。
![]()
真实世界的崩溃没有报错。Chrome的渲染进程直接死掉,用户只看到"Aw, Snap!"。这不是异常,是OOM——内存耗尽。
为什么实验室测不到
Lighthouse在隔离环境中跑单次加载,用的是全新浏览器实例。它算不出堆内存的增长曲线,测不出3GB安卓机上的标签页存活概率。OOM不会抛出JavaScript错误,进程直接终止,没有任何堆栈可追踪。
Chrome用户体验报告(CrUX)倒是采集了真实用户数据,但只记录加载体验,没有"4小时后被系统杀进程"这种指标。这个数据根本不存在于任何公开聚合报告中。
盲区就在这里:你能拿着满分上线,实际交付的软件却在全天使用的场景下逐渐劣化、最终崩溃。
OOM到底是什么
每个Chrome标签页运行在独立的渲染进程里,有内存上限。堆内存突破上限,进程就被杀掉。
上限不是固定数字。Chrome动态计算:
16GB内存的桌面机,单标签可用2-4GB;3GB内存的安卓机,Chrome可能主动杀掉后台标签——甚至不是当前活跃的那个。
performance.memory API(仅Chrome,非标准)能暴露部分限制:
totalJSHeapSize:V8已分配的堆内存
usedJSHeapSize:JS对象实际占用
jsHeapSizeLimit:硬上限,堆无法突破
典型桌面Chrome的jsHeapSizeLimit约4GB;3GB安卓设备上通常只有512MB-1GB。当usedJSHeapSize逼近这条线,崩溃随时发生。
长会话应用的三类内存陷阱
1. 路由状态堆积
SPA切换路由时,旧组件的引用如果没被正确释放,DOM节点、事件监听器、定时器全部留在内存里。用户点了100次导航,等于同时挂着100个页面的残骸。
2. 无界缓存
React Query、SWR这类数据获取库默认缓存所有响应。配置不当的话,几小时积累下来的查询结果全留在内存,没人清理。
3. 订阅泄漏
WebSocket、EventSource、RxJS订阅在组件卸载时没取消,连接和回调持续占用内存。轮询定时器同理——setInterval开了没关,每秒都在产生新数据。
生产环境怎么监控
实验室测不到,只能上真机采数据。
定期采样performance.memory,上报usedJSHeapSize的增长趋势。不是看绝对值,是看斜率——每小时增长50MB和增长500MB,完全是两种事故。
结合用户会话时长做分群:用满8小时的用户,内存曲线是不是线性攀升?特定操作后有没有异常跳变?
崩溃本身很难直接捕获,但可以通过心跳机制间接探测:页面定期上报存活状态,服务端发现某用户突然失联,结合设备信息推断OOM概率。
让应用扛住8小时
路由层面:切出页面时强制清理非必要状态,限制历史栈深度。
缓存层面:给所有查询库设TTL和上限,陈旧数据主动驱逐。不要依赖"用户会刷新页面"这种假设。
订阅层面:组件卸载生命周期里取消所有外部连接,用WeakRef管理可选缓存,让垃圾回收有机会介入。
内存预算:根据目标设备的jsHeapSizeLimit倒推安全水位,复杂应用预留50%缓冲。
Lighthouse 100分值得庆祝,但别让它成为唯一的成绩单。真正的高性能,是用户从早用到晚,标签页还活着。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.