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

相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax