视频流实战02:JS堆约占10%却OOM — 排查和内存管理 📅 发布时间:2026/9/16 7:34:59 👁 浏览次数: 一、概要基于黑屏无法播放经过证实 video.js 播放器播放的视频流格式为H.265因为 Chrome 不支持 H.265 格式视频流所以无法播放需要借助WASM解码实现在Chrome中正常播放视频。持续观察 1h左右 浏览器出现崩溃现象排查内存溢出问题。着重从浏览器的原生能力对JS堆、WASM堆、GPU三方面的内存角度进行分析。二、整体架构流程2.1 冗余的岔路口2.1.1 JS堆的内存输出结果111.1MB / 上限 1048MB10.6%。基本稳定在10% 20%持续观察出现内存崩溃setInterval((){ if(window.performance window.performance.memory){ var m window.performance.memory //Bytes var usedMB (m.usedJSHeapSize/1048576).toFixed(1) var limitMB (m.jsHeapSizeLimit/1048576).toFixed(1) var pecent (m.usedJSHeapSize/m.jsHeapSizeLimit * 100).toFix(1) console.log(内存已用 useMB MB/上限 limitMB MB ( pecent %)) } },3000)2.1.2 WASM单路的线性内存获取单路WASM线性实际内存通过 任务管理器 的 内存占用空间 观察或者根据已有数据进行大致的理论估算WebGLCONTEXT_LOST_WEBGL RangeErrorArray buffer allocation failed …… RangeError: WebAssembly.Memory(): Property maximum: value 65537 is above the upper bound 65536 // canvas 的 webglcontextlost事件表示 GPU的资源耗尽或者上下文回收 // 并且解码帧时给 WASM堆 扩容失败内存已被吃满 // 65536页 * 64KB/页 4GB2.2 解决方式2.2.1 借助 Jessibuca 中产生的误区通过调整显示尺寸解码节省内存jessibuca 没有此功能拿到视频流之后它直接按照 1080P 解码而不根据 isResize参数设置 达到在解码时降分辨率目的最后输出的帧缓冲保持1080PisResize决定的是解码后的视频显示在容器中的缩放方式true 等比缩放可能有黑边 或者 false 拉伸填满。通过后端转码方式将视频编码为主码流、子码流两路独立的视频流从而降低分辨率减少带宽避免网络堵塞引起的请求堵塞缓解客户端的压力。比如 拉取高码流视频流引起带宽紧张的压力1080P对比480P视频流 解码视频时 CPU占用率更高的压力、WebGL渲染管道存放原始数据更大的压力。OffscreenCanvas:false 无效第一点本地 Jessibuca 库把 false 强制改为truecanvas无法在Worker线程中开启渲染。第二点useMSE: true 时MSE 走 video如果 MSE 无法处理库的默认配置表autoWasm: !0官方文档jessibuca 的 FAQ明确写了该特性是实验性特性某些版本的浏览器会出现内存无缘无故变大的情况。谨慎使用。 demo/document.md · 34f752065aeab70e137d6b48fdaf1f6395ea47da · gnpm / IschoolJessibuca · GitLab2.2.2 GC延迟处理错峰重启之后依然出现页面崩溃。标记可回收之后等待5s让浏览器尽可能执行GC。scheduleRestart(){ …… this.clearRestart(); var totalDelay this.restartInterval this.restartOffset; // 定时重启错峰时间 var restartTime now Date( Date.now() totalDealy ).toLocaleTimeString(); this.$emit(restart,{restartTime: restartTime, delay:totalDedelay}) this.restartTimer setTimeout(async (){ this.$emit(restarting) await this.destoryPlayer() // this.player null await new Promsie(function(resolve){ setTimeout(resolv, 5000);}) await this.startPlayback() // 重试 },totalDelay) }2.3 技术细节浏览器的多进程架构的隔离设计单标签页中包括主线程、Worker线程、用于合成的线程。Worker线程本项目中职责是 WASM解码 和 OffsecreenCanvas渲染。主线程包括JS堆、DOM对象。将 FFmpeg 应用到Web端开发者使用 Emscripten编译器工具链将 FFmpeg源码编译为 ffmpeg.wasmFFmpegC语言开发的音视频处理框架。发展过程经过asm.js 过渡到仅适用Chrome的 NaCL最后推出 WebAssembly。WASM的单路线性内存即Emscripten堆内存Emscripten堆中是WASM解码器实例包括 管理开销 和 工作集。管理开销包括变量和引用地址、文件或者模块等元数据。细分为栈、静态区、元数据工作集包括 解码图像缓存DPB DPB参考帧分为长期、短期、不作为还包括码流或者解码的中间缓冲。三、总结分析老版Chrome中不同条件下出现视频播放 OOM 即连续播放 1h左右 标签页崩溃的原因前提1 video.js加上flv.js 方式。前提2 播放器SDK对H.265格式解码方式。着重针对报错提示 WASM堆内存 和 GPU的WebGL纹理内存占满浏览器单标签页内存 的信息进行以下的优化。多路错峰初始化initDelay时 5路视频 依次间隔 5秒 再初始化避免同时实例化WASN根据 ?/ initDelayMs120000 线上调参选择30分钟定时重启再依次错峰5分钟destory 标记回收之后等待5s让 GC 回收旧堆再重新创建 WASM实例降低GC压力获取实时数据时复用不变对象的地址减少监听和重建对象包括播放器初始化的次数即减少分配的临时对象并配合 setTimeout链式 和 pollSeq防抖避免网络不稳定引起的请求堆积。注意更合适的杠杆处理方式如下1. 将摄像头的输出格式配置为 H.264格式2. 在后端通过ffmpeg将H.264转为H.265则不需要考虑WASM堆3. 升级chrome版本。老版 或者 32bit Chrome的 JS堆上限 内存为1G左右、单实例可达上限约为2GB现代 64bit Chrome上限都约为4GB。