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

相关推荐
前端世界26 分钟前
从零搭建 ASP.NET 单文件 Web 项目:一个能真用的 BookShop 管理页实战
服务器·前端·asp.net
码上成长37 分钟前
Vue Router 3 升级 4:写法、坑点、兼容一次讲透
前端·javascript·vue.js
BBB努力学习程序设计37 分钟前
响应式页面设计与实现:让网站适配所有设备的艺术
前端·html
IT从业者张某某1 小时前
less 工具 OpenHarmony PC适配实践
前端·microsoft·less
行走的陀螺仪2 小时前
vue3-封装权限按钮组件和自定义指令
前端·vue3·js·自定义指令·权限按钮
isyuah2 小时前
vite-plugin-openapi-ts CLI 使用指南
前端·vite
qq_398586542 小时前
浏览器中内嵌一个浏览器
前端·javascript·css·css3
Mapmost2 小时前
地图引擎性能优化:解决3DTiles加载痛点的六大核心策略
前端
San30.3 小时前
Ajax 数据请求:从 XMLHttpRequest 到现代前端数据交互的演进
前端·ajax·交互
西西西西胡萝卜鸡3 小时前
虚拟列表(Virtual List)组件实现与优化铁臂猿版(简易版)
前端·vue.js