【代码篇】事件监听函数的内存泄漏,都给我退散吧!

前言

内存泄漏是个很严肃的问题,可是迄今也没有一个非常有效的排查方案,本方案就是针对性的单点突破。

工作中,我们会对window, DOM节点,WebSoket, 或者单纯的事件中心等注册事件监听函数, 添加了,没有移除,就会导致内存泄漏,如何预警,收集,排查这种问题呢?

本文是代码篇,主要讲使用和实现。

更多理论知识,请阅读理论篇 【方案篇】事件监听函数的内存泄漏,帮你搞定!

源码和demo

源码: 事件分析vem

项目内部有丰富的例子。

核心功能

我们解决问题的时机无非为 事前事中事后

我们这里主要是 事前事后

  • 事件监听函数添加前进行预警
  • 事件监听函数添加后进行统计

了解功能之前,先了解一下四同特性:

  1. 同一事件监听函数从属对象
    事件监听总是要注册到响应的对象上的, 比如下面代码的window, socket, emitter都是事件监听函数的从属对象、

    window.addEventListener("resize",onResize)socket.on("message", onMessage);emitter.on("message", onMessage);
    
  2. 同一事件监听函数类型
    这个比较好理解,比如window的 message, resize等,Audio的 play等等

  3. 同一事件监听函数内容
    这里注意一点,事件监听函数相同,分两种:

    • 函数引用相同
    • 函数内容相同
  4. 同一事件监听函数选项
    这个可选项,EventTarget系列有这些选项,其他系列没有。
    选项不同,添加和删除的时候结果就可能不通。

    window.addEventListener("resize",onResize)
    // 移除事件监听函数onResize失败
    window.removeEventListener("resize",onResize, true)
    

预警

事件监听函数添加前,比对四同属性的事件监听函数,如果有重复,进行报警。

统计高危监听事件函数

最核心的功能。
统计事件监听函数从属对象的所有事件信息,输出满足 四同属性 的事件监听函数。
如果有数据输出,极大概率,你内存泄漏了。

统计全部的事件监听函数

统计事件监听函数从属对象的所有事件信息, 可以用于分析业务逻辑。

一览你添加了多少事件, 是不是有些应该不存的,还存在呢?

基本使用

初始化参数

内置三个系列:

new EVM.ETargetEVM(options, et); // EventTarget系列
new EVM.EventsEVM(options, et); // events 系列
new EVM.CEventsEVM(options, et); // component-emitter系列

当然,你可以继承BaseEvm, 自定义出新的系列,因为上面的三个系列也都是继承BaseEvm而来。

最主要的初始化参数也就是 options

  • options.isSameOptions
    是一个函数。主要是用来判定事件监听函数的选项。
  • options.isInWhiteList
    是一个函数。主要用来判定是否收集。
  • options.maxContentLength
    是一个数字。你可以限定统计时,需要截取的函数内容的长度。

EventTarget系列

  • EventTarget
  • DOM节点 + windwow + document
  • XMLHttpRequest 其继承于 EventTarget
  • 原生的WebSocket 其继承于 EventTarget
  • 其他继承自EventTarget的对象
基本使用
<script src="http://127.0.0.1:8080/dist/evm.js?t=5"></script>
<script>const evm = new EVM.ETargetEVM({// 白名单,因为DOM事件的注册可能isInWhiteList(target, event, listener, options) {if (target === window && event !== "error") {return true;}return false;}});// 开始监听evm.watch();// 定期打印极有可能是重复注册的事件监听函数信息setInterval(async function () {// statistics getExtremelyItemsconst data = await evm.getExtremelyItems({ containsContent: true });console.log("evm:", data);}, 3000)
</script>
效果截图

截图来自我对实际项目的分析 , window对象上message消息的重复添加, 次数高达10
image.png

events 系列

  • Nodejs 标准的 events
  • MQTT 基于 events库
  • socket.io 基于 events库
基本使用
import { EventEmitter } from "events";const evm = new win.EVM.EventsEVM(undefined, EventEmitter);
evm.watch();
setTimeout(async function () {// statistics getExtremelyItemsconst data = await evm.getExtremelyItems();console.log("evm:", data);
}, 5000)
效果截图

截图来自我对实际项目的分析 ,APP_ACT_COM_HIDE_ 系列事件重复添加
image.png

component-emitter 系列

  • component-emitter
  • socket.io-client(即socket.io的客户端)
基本使用
const Emitter = require('component-emitter');
const emitter = new Emitter();const EVM = require('../../dist/evm');const evm = new EVM.CEventsEVM(undefined, Emitter);
evm.watch();// 其他代码evm.getExtremelyItems().then(function (res) {console.log("res:", res.length);res.forEach(r => {console.log(r.type, r.constructor, r.events);})})
效果截图

image.png

事件分析的基本思路

上篇总结的思路:

  1. WeakRef建立和target对象的关联,并不影响其回收
  2. 重写 EventTargetEventEmitter 两个系列的订阅和取消订阅的相关方法, 收集事件注册信息
  3. FinalizationRegistry 监听 target回收,并清除相关数据
  4. 函数比对,除了引用比对,还有内容比对
  5. 对于bind之后的函数,采用重写bind方法来获取原方法代码内容

代码结构

代码基本结构如下:

image.png

具体注释如下:

evmCEvents.ts // components-emitter系列,继承自 BaseEvmETarget.ts // EventTarget系列,继承自 BaseEvmEvents.ts  // events系列,继承自 BaseEvm
BaseEvm.ts  // 核心逻辑类
custom.d.ts 
EventEmitter.ts // 简单的事件中心
EventsMap.ts // 数据存储的核心
index.ts // 入口文件
types.ts // 类型申请
util.ts // 工具类

核心实现

EventsMap.ts

负责数据的存储和基本的统计。

数据存储结构:(双层Map)

 Map<WeakRef<Object>, Map<EventType, EventsMapItem<T>[]>>();interface EventsMapItem<O = any> {listener: WeakRef<Function>;options: O
}

内部结构的大纲如下:
image.png

方法都很好理解,大家可能注意到了,有些方法后面跟着byTarget的字样,那是因为
其内部采用Map存储,但是key的类型是弱引用WeakRef

我们增加和删除事件监听的时候,传入的对象肯定是普通的target对象,需要多经过一个步骤,通过target来查到其对应的key,这就是byTarget要表达的意思。

还是罗列一些方法的作用:

  • getKeyFromTarget
    通过target对象获得键
  • keys
    获得所有弱引用的键值
  • addListener
    添加监听函数
  • removeListener
    删除监听函数
  • remove
    删除某个键的所有数据
  • removeByTarget
    通过target删除某个键的所有数据
  • removeEventsByTarget
    通过target删除某个键某个事件类型的所有数据
  • hasByTarget
    通过target查询是否有某个键
  • has
    是否有某个键
  • getEventsObj
    获得某个target的所有事件信息
  • hasListener
    某个target是否存在某个事件监听函数
  • getExtremelyItems
    获得高危的事件监听函数信息
  • get data
    获得数据

BaseEVM

内部结构的大纲如下:

image.png

核心实现就是watchcancel,继承BaseEVM并重写这两个方法,你就可以获得一个新的系列。

统计的两个核心方法就是 statisticsgetExtremelyItems

还是罗列一些方法的作用:

  • innerAddCallback
    监听事件函数的添加,并收集相关信息
  • innerRemoveCallback
    监听事件函数的添加,并清理相关信息
  • checkAndProxy
    检查并执行代理
  • restoreProperties
    恢复被代理属性
  • gc
    如果可以,执行垃圾回收
  • #getListenerContent
    统计时,获取函数内容
  • #getListenerInfo
    统计时,获得函数信息,主要是name和content。
  • statistics
    统计所有事件监听函数信息。
  • #getExtremelyListeners
    统计高危事件
  • getExtremelyItems
    基于#getExtremelyListeners汇总高危事件信息。
  • watch
    执行监听,需要被重写的方法
  • cancel
    取消监听,需要被重写的方法
  • removeByTarget
    清理某个对象的所有数据
  • removeEventsByTarget
    清理某个对象某类类型的事件监听

ETargetEVM

我们已经提到过,实际上已经实现了三个系列,我们就以ETargetEVM为例,看看怎么通过继承和重写获得对某个系列事件监听的收集和统计。

  1. 核心就是重写watch和cancel,分别对应了代理和取消相关代理
  2. checkAndProxy是核心,其封装了代理过程, 通过自定义第二个参数(函数),过滤数据。
  3. 就这么简单
const DEFAULT_OPTIONS: BaseEvmOptions = {isInWhiteList: boolenFalse,isSameOptions: isSameETOptions
}const ADD_PROPERTIES = ["addEventListener"];
const REMOVE_PROPERTIES = ["removeEventListener"];/*** EVM for EventTarget*/
export default class ETargetEVM extends BaseEvm<TypeListenerOptions> {protected orgEt: any;protected rpList: {proxy: object;revoke: () => void;}[] = [];protected et: any;constructor(options: BaseEvmOptions = DEFAULT_OPTIONS, et: any = EventTarget) {super({...DEFAULT_OPTIONS,...options});if (et == null || !isObject(et.prototype)) {throw new Error("参数et的原型必须是一个有效的对象")}this.orgEt = { ...et };this.et = et;}#getListenr(listener: Function | ListenerWrapper) {if (typeof listener == "function") {return listener}return null;}#innerAddCallback: EVMBaseEventListener<void, string> = (target, event, listener, options) => {const fn = this.#getListenr(listener)if (!isFunction(fn as Function)) {return;}return super.innerAddCallback(target, event, fn as Function, options);}#innerRemoveCallback: EVMBaseEventListener<void, string> = (target, event, listener, options) => {const fn = this.#getListenr(listener)if (!isFunction(fn as Function)) {return;}return super.innerRemoveCallback(target, event, fn as Function, options);}watch() {super.watch();let rp;// addEventListener rp = this.checkAndProxy(this.et.prototype, this.#innerAddCallback, ADD_PROPERTIES);if (rp !== null) {this.rpList.push(rp);}// removeEventListenerrp = this.checkAndProxy(this.et.prototype, this.#innerRemoveCallback, REMOVE_PROPERTIES);if (rp !== null) {this.rpList.push(rp);}return () => this.cancel();}cancel() {super.cancel();this.restoreProperties(this.et.prototype, this.orgEt.prototype, ADD_PROPERTIES);this.restoreProperties(this.et.prototype, this.orgEt.prototype, REMOVE_PROPERTIES);this.rpList.forEach(rp => rp.revoke());this.rpList = [];}
}

总结

  • 单独设计了一套存储结构EventsMap
  • 把基础的逻辑封装在BaseEVM
  • 通过继承重写某些方法,从而可以满足不同的事件监场景。

写在最后

技术交流群请到 这里来。 或者添加我的微信 dirge-cloud,带带我,一起学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/791651.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开源软件技术社区方案

开源软件技术社区是一个由开发者、贡献者、用户和维护者组成的共享平台&#xff0c;主要目的是打造技术、软件产品良性互动、开源技术安全可控的软件生态环境&#xff0c;实现可复用应用或服务的快速部署与使用、完成资源与能力的高度共享、促进社区成员的共建共赢&#xff0c;…

c语言--枚举类型(声明、使用、优点)

目录 一、枚举类型的声明二、 枚举类型的优点三、 枚举类型的使用 一、枚举类型的声明 枚举顾名思义就是一一列举。 把可能的取值⼀⼀列举。 比如我们现实生活中&#xff1a; ⼀周的星期⼀到星期日是有限的7天&#xff0c;可以⼀⼀列举 性别有&#xff1a;男、女、保密&#x…

渐进式图片解决前端在页面中使用大图,图片体积过大导致页面出现白屏现象

1、演示 可以看到&#xff0c;图片还在拼命加载的时候&#xff0c; 页面上就已经有内容了 2、什么渐进式图片 图片一开始是模糊的&#xff0c;然后逐渐的开始变的清晰。如果页面上有一些大图&#xff0c;如果直接扔给浏览器的话那么图片的传输时间就会比较长&#xff0c;用户就…

软考高级架构师:校验码概念和例题

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

【STM32】ST-LINK 下载时遇到的问题

如果出现“ST-Link USB communication error”ST-Link USB通信错误&#xff0c;则需要启动STM32 ST-LINK Utility&#xff0c;点击【ST-LINK】->【Firmaware】更新固件&#xff0c;然后打开Kei&#xff0c;点击魔术棒->->Debug->Settings&#xff0c;开到出现类似“…

电磁兼容故障整改-静电放电抗扰度不合格

电磁兼容故障整改-静电放电抗扰度不合格 ESD干扰机理不合格的主要原因 静电放电对设备的影响有下面几种安装滤波器也对静电放电有所帮助通过良好的搭接与接地防止ESD ESD干扰机理 ESD干扰电流其实是一种共模电流&#xff0c;因为ESD电压总是以参考接地板为基准的。如空击穿放电…

从redux的基本概念渐进式理解redux/toolkit的用法

概念 Redux toolkit是帮助提高redux开发效率的一个库 React-redux 是将React和Redux toolkit绑定在一起的一个库 action 是一个对象,里面有一个type属性 action creator是一个函数,这个函数可以返回上面的action对象。 reducer 是一个函数,接受两个参数(initilastate, acti…

微软邮箱被锁住,该如何解除限制?

ChatGPT账号是微软邮箱注册的&#xff0c;我们在登陆微软邮箱时&#xff0c;http://www.outlook.com&#xff0c;不需要开魔法工具&#xff0c;直接就可以登陆&#xff0c;否则会出现安全验证&#xff0c;限制登陆。 那么如果账号被锁&#xff0c;我们该如何解除限制呢&#x…

mongoDB 优化(2)索引

执行计划 语法&#xff1a; db.collection_xxx_t.find({"param":"xxxxxxx"}).explain(executionStats) 感觉这篇文章写得很好&#xff0c;可以参考 MongoDB——索引&#xff08;单索引&#xff0c;复合索引&#xff0c;索引创建、使用&#xff09;_mongo …

asf是什么格式的文件?用手机怎么打开?

由于手机操作系统和硬件的限制&#xff0c;大部分手机并不直接支持asf文件的播放。因此&#xff0c;如果你想在手机上打开asf文件&#xff0c;你可能需要先将文件转换为手机支持的格式&#xff0c;如MP4。可以通过使用一些视频转换软件来实现&#xff0c;比如野葱视频转换器。 …

【VSCode】修改插件地址

不想放在原始C盘下面C:\Users\{用户}\.vscode\extensions为了后续存储空间考虑&#xff0c;想通过添加环境变量创建名为VSCODE_EXTENSIONS的环境变量&#xff0c;内容指向vs Code扩展所在目录即可 直接配置环境变量&#xff0c;不要在有空格的文件夹下面 变量名称&#xff1a;…

TCP的十个重要的机制

注&#xff1a;TCP不是只有十个机制 TCP 可靠传输是tcp最为重要的核心&#xff08;初心&#xff09; 可靠传输&#xff0c;并不是发送方把数据能够100%的传输给接收方 而是退而求其次 让发送方发送出去数据之后&#xff0c;能够知道接收方是否收到数据。 一但发现对方没有…

CVE-2021-30517:Type confusion bug in LoadSuperIC

前言 这个漏洞是一个比较老的洞&#xff0c;之所以分析这个漏洞&#xff0c;只要是想再学习一下 ICs 相关的知识。并该漏洞的利用是利用与 String/Function 之间的混淆&#xff0c;比较有意思。 环境搭建 sudo apt install python git checkout 7d5e5f6c62c3f38acee12dc4114…

vite.config.js

Vue3vite vite和webpack区别&#xff1f; 1.vite服务器启动速度比webpack快&#xff0c;由于vite启动的时候不需要打包&#xff0c;也就无需分析模块依赖、编译&#xff0c;所以启动速度非常快。当浏览器请求需要的模块时&#xff0c;再对模块进行编译&#xff0c;这种按需动态…

AI智能涂抹修补解决方案助力企业高效创作

传统的手动涂抹修补方式不仅效率低下&#xff0c;而且往往难以达到理想的视觉效果。美摄科技凭借深厚的AI技术研发实力&#xff0c;推出了面向企业的AI智能涂抹修补解决方案&#xff0c;为企业带来前所未有的创作体验。 美摄科技的AI智能涂抹修补解决方案&#xff0c;具备强大…

年少不知EFCore好,错把SqlSugar当成宝

背景&#xff1a;依然记得我的第一份WebApi项目使用得是SqlSugar&#xff0c;当时还没有系统学习b/s这边的知识&#xff0c;跟着别人做项目用SqlSugar觉得非常方便&#xff0c;减少了自己手写ADO.Net的痛苦。但是今天发现这个EFCore也是巨好用啊&#xff0c;下面写一下他的简单…

DDD 的四层领域模型是怎样的?包含哪些基础概念?

DDD的四层领域模型如下所示&#xff1a; 展现层&#xff1a;这一层负责向用户显示信息和解释用户命令&#xff0c;完成前端界面逻辑。并将用户请求传递给应用层。应用层&#xff1a;这一层是很薄的一层&#xff0c;负责协调领域层中的领域对象&#xff0c;组成具体应用场景。应…

LangChain入门:11.Pydantic(JSON)解析器实战

摘要 在数字化营销的浪潮中&#xff0c;自动化内容生成成为了提升效率和用户参与度的利器。本文将详细介绍如何利用LangChain的自然语言处理能力和Pydantic的数据验证特性&#xff0c;构建一个自动化的花店文案生成器。通过这个工具&#xff0c;您可以快速为各种花卉生成吸引人…

Gateway是什么?(SpringCloudAlibaba组件)

1、网关介绍 **网关(Gateway)又称网间连接器、协议转换器。网关在传输层上以实现网络互连&#xff0c;是最复杂的网络互连设备&#xff0c;仅用于两个高层协议不同的网络互连。**网关的结构也和路由器类似&#xff0c;不同的是互连层。网关既可以用于广域网互连&#xff0c;也可…

截稿倒计时 CCF-B COCOON’24论文延期至4月8日提交

会议之眼 快讯 第30届COCOON 2024 (International Computing and Combinatorics Conference)即国际计算与组合学会议将于 2024 年 8月23日-25日在中国上海举行&#xff01;COCOON是一个专注于计算机科学理论领域的国际性学术会议&#xff01;COCOON会议自1995年起举办&#xf…