如何排查 Electron V8 引发的内存 OOM 问题(下)

简介: 如何排查 Electron V8 引发的内存 OOM 问题(下)

更多精彩内容,欢迎观看:

如何排查 Electron V8 引发的内存 OOM 问题(中):https://developer.aliyun.com/article/1263249?groupCode=taobaotech


如何用 Memory 和 Performance 工具分析内存泄漏问题


前面提到,我们可以通过编译 8G 堆内存的 Electron 版本来缓解 V8FatalErrorCallback 崩溃问题,但这种解决方案会带来以下几个副作用:

  1. 享受不了 v8 指针压缩带来的好处,会额外增加至少 40% 的内存开销。
  2. 自行编译 Electron 版本后,需要定期维护更新升级,存在潜在的风险。
  3. 无法彻底解决 V8FatalErrorCallback 崩溃问题。


因此,我们还是要透过现象去分析问题发生的本质原因。对于客户端应用使用过程中 v8 堆内存一直持续增长问题,根本原因是因为内存泄漏导致。那有人就会问了,v8 不是会自动帮我们 GC 垃圾回收不再使用的内存吗?为啥还会一直持续增长呢?


我们先来看下内存泄漏的定义:当进程不再需要某些内存时,依然没办法回收这些内存。在 JavaScript 中,造成内存泄漏的主要原因是不再需要的内存数据仍然被其他对象引用着。也就是说,这些内存数据里的对象不再使用但引用计数不为 0 导致无法被 GC,造成内存泄漏。


那要如何定位排查出哪块代码导致的内存泄漏呢?我们可以使借助 chrome devtools 提供的 Memory 和 Performance 工具来分析内存泄漏问题。


 如何用 Memory 工具分析内存泄漏问题


我们首先可以通过 Memory 工具,从内存中对象的角度来分析内存泄漏。


首先点击 chrome Memory 面板下的垃圾回收按钮,手动触发一次 GC:


然后录制一次内存快照,过一段时间(如 1min)后再录制一次内存快照。通过这两次内存快照可以得知内存增长了 19.2M,存在内存泄漏的问题。

继续对比 diff 这两次内存快照,发现最大的内存增长是 string 对象。但点 string tab 展开后并不能直接定位到是哪块代码逻辑导致,只能去排查到底哪里在频繁使用string 对象导致的内存泄漏,定位问题不够直观。

 如何用 Performance 工具分析内存泄漏问题


因此,我们还需要通过 Performance 工具,从代码的角度来分析内存泄漏。

首先点击 chrome Performance 面板下的垃圾回收按钮,手动触发一次 GC:

然后勾选 chrome Performance 面板下的 Memory 选项,点击录制按钮开始录制,等录制一段时间(如 1 分钟)后停止录制:

接着查看 chrome Performance 面板下的内存部分,只勾选 JS Heap 看下内存是否有增长趋势。下图显示内存从 24.1M 一直增长到 38.1M,说明存在内存泄漏。

紧接着点击内存分配情况的某个点,就会定位到 Performance 中的某个任务的代码。

最后点击某个任务代码可以定位到分配内存的代码,分析后发现是一直在触发 electron-log 的 onError 事件,具体的应用代码如下:


import TraceSdk from '@ali/trace-sdk-node'import log from 'electron-log'
// arms 实时日志上报平台let trace = TraceSdk()const sendErrorLog = trace.logError
log.catchErrors({  onError(error) {    sendErrorLog(error)  },})


通过查看 V8FatalErrorCallback 这类崩溃用户日志发现崩溃前一直在上报 FetchError 的错误信息:

Unhandled Exception FetchError: request to https://s-gm.mmstat.com/arms.1.1 failed, reason: getaddrinfo ENOTFOUND s-gm.mmstat.com    at ClientRequest.<anonymous> (http://localhost:2546/home.js:7090:298490)    at ClientRequest.emit (node:events:390:28)    at TLSSocket.socketErrorListener (node:_http_client:447:9)    at TLSSocket.emit (node:events:390:28)    at emitErrorNT (node:internal/streams/destroy:157:8)    at emitErrorCloseNT (node:internal/streams/destroy:122:3)    at processTicksAndRejections (node:internal/process/task_queues:83:21)



另外看下面的调用堆栈猜测可能是主进程被挂起或断网导致 arms 实时日志上报请求会一直失败,然后就会递归触发 electron-log 的 onError 事件:

为了验证这个猜想,我们尝试断网后用 vscode 断点调试发现确实如此,难怪用 Memory 工具对比分析前后一段时间的内存 heapsnapshot 时发现 string 和 array 对象一直在增长,是因为 error 里持有了错误信息的字符串。


既然定位到原因了,那解决起来也很简单,就是把 arms fetchError 这类错误日志过滤掉。不仅可以解决递归 onError 事件导致的内存泄漏问题,还可以过滤由于 arms 本身带来的错误日志。


import TraceSdk from '@ali/trace-sdk-node'import log from 'electron-log'
// arms 实时日志上报平台let trace = TraceSdk()const sendErrorLog = trace.logError
log.catchErrors({  onError(error) {    // 过滤 arms fetch error 错误日志    if (!error?.message.includes('https://s-gm.mmstat.com/arms')) {      sendErrorLog(error)    }  },})


排查这么久,V8FatalErrorCallback js heap OOM 崩溃问题终于破案了。工欲善其事必先利其器,不得不说 chrome devtools 的 Memory 和 Performance 工具对分析内存泄漏问题太有帮助了。


如何监控 v8 堆内存泄漏问题

上一章节我们通过借助 chrome devtools 的 Memory 和 Performance 工具手动分析了 1 例内存泄漏问题,但可能还存在其他如全局变量、变量被闭包引用、游离的 DOM 元素被变量引用、定时器没清除等隐藏的内存泄漏问题。当客户端应用发布后,我们要如何监控线上 v8 堆内存泄漏问题呢?

 如何监控 v8 实时堆内存使用趋势


首先我们可以通过 v8.getHeapStatistics 接口,每隔一段时间(如 1 分钟)采集一次 v8 的堆内存数据,然后上报到 Medialab平台进行监控,实时统计 v8 堆内存使用趋势,下图可以明显发现 v8 堆内存使用一直在增长,存在内存泄漏问题。

 如何监控 v8 堆内存泄漏问题


除了用 v8.getHeapStatistics 接口实时监控 v8 堆内存使用趋势外,我们还可以用 node-memwatch npm 包查找代码中的内存泄漏问题。


node-memwatch 可以监听两个事件:


stats:GC 事件,每执行一次 GC 都会触发该函数并打印 heap 相关的信息,如下所示:



{  num_full_gc: 1,// 完整的垃圾回收次数  num_inc_gc: 1,// 增长的垃圾回收次数  heap_compactions: 1,// 内存压缩次数  usage_trend: 0,// 使用趋势  estimated_base: 5350136,// 预期基数  current_base: 5350136,// 当前基数  min: 0,// 最小值  max: 0// 最大值}


leak:内存泄露事件,触发该事件的条件是:连续 5 次 GC 后内存都是增长的,如下所示:

{  growth: 4051464,  reason: 'heap growth over 5 consecutive GCs (2s) - -2147483648 bytes/hr'}



当监听到 leak 内存泄漏事件时,我们可以通过 HeapDiff 来对比分析前后内存快照,以此排查具体是哪个对象发生了内存泄漏。


{  "before": { "nodes": 11625, "size_bytes": 1869904, "size": "1.78 mb" },  "after":  { "nodes": 21435, "size_bytes": 2119136, "size": "2.02 mb" },  "change": { "size_bytes": 249232, "size": "243.39 kb", "freed_nodes": 197,    "allocated_nodes": 10007,    "details": [      { "what": "String",        "size_bytes": -2120,  "size": "-2.07 kb",  "+": 3,    "-": 62      },      { "what": "Array",        "size_bytes": 66687,  "size": "65.13 kb",  "+": 4,    "-": 78      },      { "what": "LeakingClass",        "size_bytes": 239952, "size": "234.33 kb", "+": 9998, "-": 0      }    ]  }}



总结


本文从 Electron V8FatalErrorCallback 崩溃问题的堆栈分析开始讲起,然后通过堆栈信息一步步使用各种解决方案都无功而返后,尝试分析 v8 源码堆内存限制的实现原理,并开始编译关闭指针压缩的 Electron 源码来提升 v8 堆内存上限以此来延缓问题。但由于该方案会额外带来一些副作用被否掉,最终借助 chrome devtools 提供的 Memory 和 Performance 工具一步步排查定位才解决了 Electron v8 引发的内存 OOM 问题。最后我们通过 v8.getHeapStatistics 来监控 v8 堆内存趋势、node-memwatch 来监控 v8 堆内存是否有泄漏,可以触类旁通解决其他内存 OOM 问题。


团队介绍


我们是大淘宝技术淘宝直播前端团队,负责淘系增长非常快的直播业务,业务上升空间非常大。在技术方面,我们在探索直播间互动、游戏互动、数据可视化、音视频播放器、微前端、智能搭建、Web 3D、Electron跨端开发、桌面推流客户端开发、跨 PC/H5/Native 的多端架构等。在这里你有机会通过一行代码为业务创造亿级 GMV 增量,期待优秀的你!


相关文章
|
5天前
|
Web App开发 JavaScript Java
如何排查 Electron V8 引发的内存 OOM 问题
如何排查 Electron V8 引发的内存 OOM 问题
|
1月前
|
缓存 算法 安全
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(二)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
28 0
|
1月前
|
缓存 Java C#
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(一)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
103 0
|
1月前
|
Java Linux Arthas
linux上如何排查JVM内存过高?
linux上如何排查JVM内存过高?
1040 0
|
1月前
|
缓存 Java 开发工具
OOM out of memory 内存溢出
OOM out of memory 内存溢出
26 1
|
1月前
|
存储 监控 Java
三万字长文:JVM内存问题排查Cookbook
本文主要系统性地整理了排查思路,为大家遇到问题时提供全面的排查流程,不至于漏掉某些可能性误入歧途浪费时间。
152 1
|
1月前
|
缓存 NoSQL 中间件
redis内存溢出报错--OOM command not allowed when used memory > 'maxmemory'
该内容是关于Redis缓存服务器的使用指南。通过Xshell连接IP地址为25.218.153.193或206的主机,进入/data/iuap/middleware/redis-30001/bin目录,使用`redis-cli`连接到IP为206的30003端口。登录时需`auth yonyou*123`,可运行`info`和`info memory`查看状态,`flushall`清理缓存。在清理前,要备份/data/iuap/middleware/redis-30003/data/下的.aof和.rdb文件,利用tar命令打包并移至/tmp目录。
|
1月前
|
存储 缓存 监控
深入解析linux内存指标:快速定位系统内存问题的有效技巧与实用方法(free、top、ps、vmstat、cachestat、cachetop、sar、swap、动态内存、cgroops、oom)
深入解析linux内存指标:快速定位系统内存问题的有效技巧与实用方法(free、top、ps、vmstat、cachestat、cachetop、sar、swap、动态内存、cgroops、oom)
422 0
|
1月前
|
缓存 监控 Java
深入剖析JVM的OOM | 内存溢出如何影响JVM运行及应对策略
深入剖析JVM的OOM | 内存溢出如何影响JVM运行及应对策略
106042 1
|
2天前
|
消息中间件 存储 Kafka
实时计算 Flink版产品使用问题之 从Kafka读取数据,并与两个仅在任务启动时读取一次的维度表进行内连接(inner join)时,如果没有匹配到的数据会被直接丢弃还是会被存储在内存中
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。

热门文章

最新文章