潜在的内存泄露

350 阅读5分钟

潜在的内存泄露

本文正在参与 “性能优化实战记录”话题征文活动

引言

什么是内存泄露?

系统进程不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak). 当内存占用越来越高,轻则影响系统性能,重则导致进程崩溃. 站在前端的视角.写的不好的 JavaScript 可能出现难以察觉且有害的内存泄露问题:

  • 白屏 / 黑屏
  • 卡顿 / 页面加载迟缓
  • 闪退 / 发热
  • 💥

哪些操作会造成内存泄露

一、意外声明全局变量

意外声明全局变量是最常见但也最容易解决的内存泄露问题. 在 window上创建的属性,只要 window 本身不被清除就不会消失. 无法做到有效地释放清除

  1. 代码编写
function t(){
   name = 'renlingxin'
}
//name 变量会挂在到window上 而window上的变量在窗口关闭或重新刷新页面之前并不会销毁
// 用let const var 代替

  1. 其他挂载

插件 / 业务场景中有时会通过挂载到 window 的属性 实现运行环境下 不同工程之间地相互通信 例如

  1. WebView / JavaScript
  2. Cocos / JavaScript
  3. SDK - A / SDK - B
  4. ...

我们需要合理正确的挂载. 避免挂载大体积的数据.

二、闭包

function d(){
  let name = 'renlingxin'
  return function r(){
    console.log(name)
  }
}
//这会导致分配给name的内存被泄露.如果name很大.就会是一个大问题.

三、定时器(做到及时的clear)

保持良好的代码习惯.


<template>
 <div>{{ timeDom }}</div>
</template>

<script>
  mounted() {
    this._time = setInterval(() => {
      this.timeDom  = new Date()
    }, 1000);
  },
  // beforeDestroy 销毁之前的定时器
  beforeDestroy() {
    clearInterval(this._time)
  }
</script>

四、事件监听器(例如vue的eventBus)


import eventBus from './eventBus'

export default {
  mounted() {
    // 添加 eventBus 监听器
    eventBus.$on('page-change', () => {
      console.log('page-change triggered!')
    })
  },
  beforeDestroy() {
    // 移除 eventBus 监听器
    eventBus.$off('page-change')
  }
}

五、变量和变量 / 变量和Dom 相互引用

我们来看一个例子:

  1. 在控制台执行 node --expose-gc
> process.memoryUsage()
{
  rss: 143835136,
  heapTotal: 5054464,
  heapUsed: 3446128,
  external: 1668772,
  arrayBuffers: 9917
}
> let y = new Array(5*1024*1024)//1. 创建一个5*1024*1024大小的数组
undefined
> let g = {d:y,g:'renlingxin'} //2. 设置g中的key - d 值为y 建立强引用关系
undefined
> process.memoryUsage()  //3. 查看当前内存为 43.48 M 左右
{
  rss: 325320704,
  heapTotal: 47263744,
  heapUsed: 45596216,
  external: 1668828,
  arrayBuffers: 9933
}
> y = g.d = null  // 4. 将 y 和 g.d 设置null 解除引用关系
null
> global.gc()   // 5. 手动触发一次 gc 回收
undefined
> process.memoryUsage() // 6. 内存缩小至 3.02 M 左右
{
  rss: 140541952,
  heapTotal: 5316608,
  heapUsed: 3171248,
  external: 1668812,
  arrayBuffers: 9917
}

以上我们可以得出结论 变量之间的相互引用 以及 主动释放引用关系从而触发GC回收的重要性

  1. 可以使用 weakMap / weakSet 建立弱引用关系
> process.memoryUsage()  //1. 操作之前查看初始内存大小约为 3.28M
{
  rss: 144654336,
  heapTotal: 5054464,
  heapUsed: 3447472,
  external: 1668772,
  arrayBuffers: 9917
}
> let t = new WeakMap() //2. 新建 WeakMap 
undefined
> let y = new Array(5*1024*1024) //3. 新建 5*1024*1024 的数组
undefined
> t.set(y,'333')   // 4. 设置 y 至 WeakMap
//WeakMap { <items unknown> }
> process.memoryUsage() // 5. 当前内存大小约为 43.22M
{
  rss: 329957376,
  heapTotal: 47001600,
  heapUsed: 45320184,
  external: 1668824,
  arrayBuffers: 9929
}
> global.gc()  // 6. 主动触发gc回收
undefined
> process.memoryUsage() //7. 内存无变化
{
  rss: 332529664,
  heapTotal: 47263744,
  heapUsed: 45110040,
  external: 1668812,
  arrayBuffers: 9917
}
> y = null // 8. 将 y 原始值 清空为null.但是这里我们并没有去清空 t 中的 y
null
> global.gc() // 9. 释放内存
undefined
> process.memoryUsage() //10. 查看内存大小约为 3.07M
{
  rss: 161005568,
  heapTotal: 5316608,
  heapUsed: 3227656,
  external: 1668815,
  arrayBuffers: 9920
}

六、console(浏览器会记忆输出的变量。避免在线上环境打印)

uglifyjs-webpack-plugin / transform-remove-console ... 等方法都可以实现移除 log

调试工具

一、Performance

全面的性能监控工具

Snipaste_2021-10-12_20-41-03.png

名词解释

  • Summary:各指标时间占用统计报表
  • Bottom-Up:事件时长排序列表
  • Call tree:事件调用顺序列表
  • Event Log:事件发生的顺序列表

Ps:当你的Js Heap 有了这样的走势.那么就会有内存泄露的风险

WeChat6c51c579584ea467c6c327b30278c093.png

二、 Memory

快照功能

Snipaste_2021-10-12_20-47-52.png

Snipaste_2021-10-12_20-49-45.png

  1. 开始拍照

  2. 全部删除

  3. 功能:

    • Summary(概要) - 通过构造函数名分类显示对象;
    • Comparison(对照) - 显示两个快照间对象的差异;
    • Containment(控制) - 可用来探测堆内容;
  4. Constructor(构造函数)表示所有通过该构造函数生成的对象

  5. Distance 到 GC roots (GC 根对象)的距离。GC 根对象在浏览器中一般是 window 对象,在 Node.js 中是 global 对象

  6. Shallow size 列显示了由对应构造函数生成的对象的shallow sizes(直接占用内存)总数 --- 对象自身的大小,不包括它引用的对象单位是字节

  7. Retained size列展示了对应对象所占用的最大内存 ---- 对象自身的大小和它引用的对象的大小,即该对象被 GC 之后所能回收的内存大小。单位是字节

  8. 内存类型

    • Compiled code 编译代码
    • closure 闭包
    • HTMLDivElement、HTMLAnchorElement、DocumentFragment等等这些其实就是你的代码中对元素的引用或者指定的 DOM 对象引用 ...

三、Performance monitor

Snipaste_2021-10-12_20-55-58.png

实时监控工具.参考指标有 JavaScript 内存 / CPU 占用率 / DOM 节点 / 事件监听 ...

结论

内存泄露这个话题在前端提及的频率不是很多.大多数的业务场景是简单的页面h5的开发.但对于逻辑复杂 / 沉淀多 / 动画丰富等场景内存管理便尤其重要. 并且造成内存泄露的原因很多是积少成多的问题叠加.造成积重难返的境遇.因此需要我们从日常写码中重视内存管理.