JS内存泄露如何检测?场景有哪些?

内存泄漏:当一个对象不再被使用,但是由于某种原因,它的内存没有被释放,这就是内存泄漏。

1. 垃圾回收机制

  • 对于在JavaScript中的字符串,对象,数组是没有固定大小的,只有当对他们进行动态分配存储时,解释器就会分配内存来存储这些数据,当JavaScript的解释器消耗完系统中所有可用的内存时,就会造成系统崩溃。
  • 内存泄漏,在某些情况下,不再使用到的变量所占用内存没有及时释放,导致程序运行中,内存越占越大,极端情况下可以导致系统崩溃,服务器宕机。
  • JavaScript有自己的一套垃圾回收机制,JavaScript的解释器可以检测到什么时候程序不再使用这个对象了(数据),就会把它所占用的内存释放掉。
  • 针对JavaScript的垃圾回收机制有以下两种方法(常用):标记清除(现代),引用计数(之前)

有两种垃圾回收策略:

  • 标记清除:标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁。
  • 引用计数:它把对象是否不再需要简化定义为对象有没有其他对象引用到它。如果没有引用指向该对象(引用计数为 0),对象将被垃圾回收机制回收

标记清除的缺点:

  • 内存碎片化,空闲内存块是不连续的,容易出现很多空闲内存块,还可能会出现分配所需内存过大的对象时找不到合适的块。
  • 分配速度慢,因为即便是使用 First-fit 策略,其操作仍是一个 O(n) 的操作,最坏情况是每次都要遍历到最后,同时因为碎片化,大对象的分配效率会更慢。

解决以上的缺点可以使用 标记整理(Mark-Compact)算法 标记结束后,标记整理算法会将活着的对象(即不需要清理的对象)向内存的一端移动,最后清理掉边界的内存(如下图)

引用计数的缺点:

  • 需要一个计数器,所占内存空间大,因为我们也不知道被引用数量的上限。
  • 解决不了循环引用导致的无法回收问题
    • IE 6、7JS对象和DOM对象循环引用,清除不了,导致内存泄露

V8 的垃圾回收机制也是基于标记清除算法,不过对其做了一些优化。

  • 针对新生区采用并行回收。
  • 针对老生区采用增量标记与惰性回收

注意闭包不是内存泄露,闭包的数据是不可以被回收的

拓展:WeakMap、WeakMap的作用

  • 作用是防止内存泄露的
  • WeakMapWeakMap的应用场景
    • 想临时记录数据或关系
    • vue3中大量使用了WeakMap
  • WeakMapkey只能是对象,不能是基本类型

2. 如何检测内存泄露

内存泄露模拟

<p>
  memory change
  <button id="btn1">start</button>
</p>

<script>
    const arr = []
    for (let i = 0; i < 10 * 10000; i++) {
      arr.push(i)
    }

    function bind() {
      // 模拟一个比较大的数据
      const obj = {
        str: JSON.stringify(arr) // 简单的拷贝
      }

      window.addEventListener('resize', () => {
        console.log(obj)
      })
    }

    let n = 0
    function start() {
      setTimeout(() => {
        bind()
        n++

        // 执行 50 次
        if (n < 50) {
          start()
        } else {
          alert('done')
        }
      }, 200)
    }

    document.getElementById('btn1').addEventListener('click', () => {
      start()
    })
</script>

打开开发者工具,选择 Performance,点击 Record,然后点击 Stop,在 Memory 选项卡中可以看到内存的使用情况。

3. 内存泄露的场景(Vue为例)

  • 被全局变量、函数引用,组件销毁时未清除
  • 被全局事件、定时器引用,组件销毁时未清除
  • 被自定义事件引用,组件销毁时未清除
<template>
  <p>Memory Leak Demo</p>
</template>

<script>
export default {
  name: 'Memory Leak Demo',
  data() {
    return {
      arr: [10, 20, 30], // 数组 对象
    }
  },
  methods: {
    printArr() {
      console.log(this.arr)
    }
  },
  mounted() {
    // 全局变量
    window.arr = this.arr
    window.printArr = ()=>{
      console.log(this.arr)
    }

    // 定时器
    this.intervalId = setInterval(() => {
      console.log(this.arr)
    }, 1000)

    // 全局事件
    window.addEventListener('resize', this.printArr)
    // 自定义事件也是这样
  },
  // Vue2是beforeDestroy
  beforeUnmount() {
    // 清除全局变量
    window.arr = null
    window.printArr = null

    // 清除定时器
    clearInterval(this.intervalId)

    // 清除全局事件
    window.removeEventListener('resize', this.printArr)
  },
}
</script>

4. 拓展 WeakMap WeakSet

weakmap 和 weakset 都是弱引用,不会阻止垃圾回收机制回收对象。

const map = new Map() 
function fn1() {
  const obj = { x: 100 }
  map.set('a', obj) // fn1执行完 map还引用着obj
}
fn1()

const wMap = new WeakMap() // 弱引用
function fn1() {
  const obj = { x: 100 }
  // fn1执行完 obj会被清理掉
  wMap.set(obj, 100) // weakMap 的 key 只能是引用类型,字符串数字都不行
}
fn1()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值