浏览器处理大文件时,文件在磁盘上的体积只是输入量,不是运行时所需内存。解码、算法工作区、导出结果与预览可能同时存在;进程还受到设备可用内存和实现限制。应先找出失败阶段,才能判断该降分辨率、减并发,还是更换处理方式。
一张 12 MB 照片为什么可能需要几百 MB?
假设 JPEG 的尺寸是 6000 × 4000。只按常见的 8 位 RGBA 表示,一份像素数据就是 6000 × 4000 × 4 = 96,000,000 字节,约 91.6 MiB。如果解码图、Canvas 和 getImageData() 返回的像素各有一份,总量就约 274.7 MiB,还没算输入、编码器工作区、输出和浏览器本身。具体实现可能共享资源,也可能产生额外副本,这是一种容量估算而非内存测量。
| 输入类型 | 应估算什么 | 示例或边界 |
|---|---|---|
| 图片 | 宽 × 高 × 每像素字节数 × 同时存在的表面 | 降低 JPEG quality 不会减少原图解码像素数 |
| 音频 | 采样率 × 秒数 × 声道数 × 每采样字节数 | 10 分钟、48 kHz、双声道 Float32 约 219.7 MiB |
| ZIP | 实际解压字节数、条目数和并发 | 压缩包大小不能给出可靠的展开上限 |
| JSON | 文本、对象树、格式化输出和渲染节点 | 对象数量与嵌套结构都会影响开销 |
把图片宽和高都缩为一半,目标像素数变为四分之一;但如果库必须先完整解码原图,再缩小输出,输入端峰值仍然存在。限制源图尺寸与减少输出尺寸是两个不同的控制点。
崩溃、卡死和泄漏是三种不同问题
| 现象 | 可能原因 | 先验证什么 |
|---|---|---|
| 某个文件第一次就让标签页退出 | 瞬时工作集过大、编解码器故障、系统终止 | 尺寸、阶段、相同文件是否可重复 |
| 点击后界面无响应但最终完成 | 主线程长任务 | Performance 中的调用栈和任务时长 |
| 连续处理多次才失败 | 结果积累或资源泄漏 | 清空后基线是否逐轮增长 |
| JS 堆不高但进程内存很高 | 像素、原生缓冲、GPU 或 WASM 资源 | 进程指标与各资源生命周期 |
没有一个适用于所有浏览器的“超过 500 MB 必崩溃”阈值。操作系统、手机后台策略、其他标签页、引擎版本、连续大块内存分配和 Canvas 尺寸限制都可能改变结果。try/catch 能处理部分分配或解码错误,但不能保证捕获系统直接终止渲染进程的情况。
按阶段定位,而不是只看最终报错
- 建立最小复现:固定一个文件,记录字节数、像素尺寸或时长、浏览器和设备,先只运行一个任务。
- 给读取、解码、变换、编码、预览标记开始与结束时间,找出最后成功的阶段。
- 用浏览器任务管理器观察进程内存,用 DevTools Performance 查看长任务;需要查 JS 引用时再比较堆快照。
- 重复“处理—清空”十轮,比较清理后的基线;再分别把分辨率、时长和并发减半,观察哪项改变失败点。
- 在低内存目标设备上复测,并测试取消、坏文件和多次重试,避免只验证开发电脑上的单次成功。
堆快照中的保留路径能解释为什么对象仍被 state、闭包或监听器持有,但快照本身也有成本。不要在已经接近内存极限的任务上频繁抓快照。开发工具还可能保留你在控制台查看过的大对象,最好再在关闭开发工具的条件下复测真实用户流程。
让限制与实际工作量相关
图片应限制像素数,音频应限制时长和声道,压缩包应同时限制条目数、单项和总展开字节数。对解压任务要在实际输出过程中计数并中止,不能只相信文件头声明的大小。恶意压缩包可能很小,却不断产生输出。
建立产品预算时,可以用“基础占用 + 并发任务数 × 单任务工作集 + 保留结果 + 余量”估算。假设实测单任务峰值增量是 250 MiB,并发四个可能接近额外 1 GiB;真实重叠程度需测量。先降低并发、清理结果,再调整功能上限,通常比仅换一个 Worker 更直接。
普通用户如何完成这次任务?
关闭不需要的标签页能腾出空间,但无法改变任务本身的最低工作集。优先拆分批次、提前缩小源图或裁短音频、关闭不必要的实时预览;如果单文件仍超出能力,就转到支持该规模的桌面工具或合适的服务端流程。重试前先保留原文件,不要不断在已经积累输出的页面里重新运行。