前端OOM内存泄漏如何排查?

前言

现代前端开发中,随着应用的复杂性和交互性的增加,OOM(Out Of Memory,内存不足)问题和内存泄漏逐渐成为影响用户体验和应用性能的关键挑战。排查和解决这些问题需要开发人员具备良好的调试技巧和优化策略。

造成OOM的一些原因

1、 未销毁的事件监听器

事件监听器是常见的内存泄漏源。当你在DOM元素上添加事件监听器时,如果不手动删除它们,它们将一直存在于内存中,即使元素被销毁了。

js 复制代码
// 内存泄漏示例
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
  // 处理点击事件
});

解决方法:在组件卸载或不再需要时,务必记得删除事件监听器。

js 复制代码
// 解决内存泄漏
const button = document.getElementById('myButton');
const handleClick = () => {
  // 处理点击事件
};
button.addEventListener('click', handleClick);

// 在组件卸载或不再需要时,删除事件监听器
button.removeEventListener('click', handleClick);

2、 引用计数循环

循环引用是另一个常见的内存泄漏源。当两个或多个对象相互引用时,并且没有任何引用指向它们之中的任何一个时,它们将无法被垃圾回收。

js 复制代码
// 内存泄漏示例
function createObjects() {
  const obj1 = {};
  const obj2 = {};
  obj1.ref = obj2;
  obj2.ref = obj1;
}
createObjects();

解决方法:避免循环引用,或者在不再需要这些引用时手动解除它们。

js 复制代码
// 解决内存泄漏
function createObjects() {
  const obj1 = {};
  const obj2 = {};
  obj1.ref = obj2;
  obj2.ref = obj1;
  // 不再需要 obj1 和 obj2 的引用时,将它们设为 null
  obj1.ref = null;
  obj2.ref = null;
}
createObjects();

3、未清理的定时器和回调

使用 setIntervalsetTimeout 来执行循环或延时操作时,如果忘记清理它们,可能导致持续的内存占用。

js 复制代码
// 内存泄漏示例 
const intervalId = setInterval(() => { // 执行重复任务 }, 1000);

解决方法:在组件卸载或不再需要定时器时,清除它们。

js 复制代码
// 解决内存泄漏
const intervalId = setInterval(() => {
  // 执行重复任务
}, 1000);

// 在组件卸载或不再需要时,清除定时器
clearInterval(intervalId);

4、未清理的全局变量

使用全局变量保存大量数据可能导致内存过度使用,因为这些变量不会自动释放。

js 复制代码
// 内存泄漏示例
let dataCache = fetchData();

解决方法:使用局部变量或尽可能减少全局变量的使用,确保在不需要时明确清理。

js 复制代码
// 解决内存泄漏
function handleData() {
  const dataCache = fetchData();
  // 使用完后清理
  processData(dataCache);
}

5、未释放的 DOM 元素

创建并打算随时间动态更新的 DOM 元素可能会造成内存问题,如果这些元素在不再需要时没有被移除。

js 复制代码
// 内存泄漏示例
const element = document.createElement('div');
document.body.appendChild(element);
// 未移除时此元素可能一直占用内存

解决方法:管理 DOM 元素的生命周期,确保在设备卸载时移除不必要的元素。

js 复制代码
// 解决内存泄漏
const element = document.createElement('div');
document.body.appendChild(element);
// 不再需要时移除
document.body.removeChild(element);

6、未清理的闭包

JavaScript 中的闭包允许函数访问外部作用域,但如果这些闭包长期存在并引用大量数据会导致内存泄漏。

js 复制代码
// 内存泄漏示例
function createClosure() {
  const largeObject = new Array(10000).fill('memory-leak');
  return function() {
    console.log(largeObject);
  };
}
const closureFn = createClosure();

解决方法:确保在不再需要这些闭包时,声明周期结束时移除相关引用。

js 复制代码
// 解决内存泄漏
function createClosure() {
  const largeObject = new Array(10000).fill('memory-leak');
  const closureFn = () => console.log(largeObject);
  // 用完后尽可能清理引用
  return closureFn();
}

排查和定位

1、chrome performance观察GC前后视图

利用performance在页面一次渲染后执行gc,观察渲染前和渲染后gc的内存占用情况,可以判断应用是否存在oom的情况。

首先打开Chrome Devtool 开发者工具,点击进入到 Performance 面板,勾选上 ScreenshotsMemory 选项,点击箭头所指的 record 按钮开始记录页面参数信息,在此过程中可以进行一些内存泄漏相关的可疑操作,方便后续的分析。

录制中执行一次gc,观察两段线的高度即可,如果会出现递增的情况,则可推断页面存在内存泄漏,可进行代码排查。

2、更精准的定位------chrome memory观察接近时间段的内存占用情况

通过chrome提供的内存快照可实时观测应用的内存占用情况。

我们在测试的应用中插入一段造成OOM的代码:

tsx 复制代码
import React, { useEffect, useState } from 'react';

const MemoryLeakTest: React.FC = () => {
  const [data11111, setData11111] = useState<string[]>([]);
  const [counter, setCounter] = useState<number>(0);

  useEffect(() => {
    let intervalId: ReturnType<typeof setInterval>;

    // 模拟持续不断地增加数据
    intervalId = setInterval(() => {
      const newData = Array.from({ length: 100000 }, () => Math.random().toString());
      setData11111(prevData => [...prevData, ...newData]);
      setCounter(counter + 1);
    }, 100); // 每100毫秒添加一批新数据

    // 不执行清理函数以模拟内存泄露
    return () => {
      // clearInterval(intervalId); // 注释掉清理函数
    };
  }, [counter]); // 注意这里的依赖项,确保每次计数器变化都会重新设置定时器

随后进行三次快照,对比排查,发现每一次的快照内存占用都会线性递增,在Delta列降序排列,第一条就可以找到罪魁祸首。仔细顺着堆内存栈往下排查,能找到罪魁祸首setData11111触发了多次更新。

结尾

本文以OOM为题,梳理了开发中会造成内存泄漏的情况、最后基于chrome performance、memory两个工具进行OOM排查定位分析,我们在日常开发中也需要在每次迭代后回归核心页面的基本性能,防止不必要的线上客诉。

相关推荐
+VX:Fegn089539 分钟前
计算机毕业设计|基于springboot + vue蛋糕店管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·课程设计
半个落月1 小时前
LangChain.js Agent Memory 实战(下):用 Milvus 构建可检索的长期记忆
javascript·langchain
半个落月1 小时前
LangChain.js Agent Memory 实战(上):从内存对话、文件持久化到截断与摘要
javascript·langchain
Nicander2 小时前
我给 Excalidraw 做了一个本地工作区:DrawSpace
前端·开源
linux_cfan4 小时前
17 · 引擎适配器全景:HLS/DASH/Vimeo/Mux/Cast
前端·javascript·音视频
HwJack204 小时前
【HarmonyOS开发小实践】ArkUI 动画系统属性动画、显式动画与路径动画
ui·性能优化·harmonyos
计算机魔术师4 小时前
烧掉2780亿美元还不够?OpenAI的资本豪赌让人头皮发麻
前端
IT_陈寒5 小时前
Java线程池用错参数,我的服务居然悄悄崩溃了
前端·人工智能·后端
卡布鲁5 小时前
React useMemo 与 useCallback 完全指南:原理、场景与避坑
前端·react.js
碳基修炼6 小时前
排查记:本地 devServer 是 http,浏览器却把 302 重定向升级成了 https
前端·http