前端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排查定位分析,我们在日常开发中也需要在每次迭代后回归核心页面的基本性能,防止不必要的线上客诉。

相关推荐
袁煦丞1 分钟前
MQTT轻松远程访问——EMQX服务器 :cpolar内网穿透实验室第548个成功挑战
前端·程序员·远程工作
超级白的小白1 分钟前
0️⃣harmany OS:华为测试机根目录安装CA证书(升级后系统禁用直接写入文件操作解决方案)
前端·harmonyos
wordbaby2 分钟前
前端动态导入(import.meta.glob)
前端·vite
蒜香拿铁3 分钟前
【前端脚手架搭建】看完还学不会,你顺着网线来打我
前端·javascript
超级白的小白3 分钟前
React实现SSR及注意事项
前端
LanceJiang3 分钟前
Element-Plus 二次封装 el-table LeTable组件
前端·vue.js
梅一一4 分钟前
一个b站偷懒工具
javascript·后端
歌呜啊瓜4 分钟前
所以,Hook 究竟是什么?
前端·react.js
超级白的小白5 分钟前
CSR、SSR、React同构概念的理解与梳理
前端
珹洺8 分钟前
从 HTML 到 CSS:开启网页样式之旅(八)—— 解决浮动产生的影响与浮动例题(CSS基础完结篇)
前端·javascript·css·servlet·html·html5