内存泄露(白屏)定位

什么是内存泄露

  • 代码的运行离不开内存,JavaScript程序每次创建字符串、数组或对象时都会分配内存来存储实体。

  • 但是我们的内存是有额度的,不能无限使用,如果一个对象不被使用时,内存就会被释放掉。

  • JavaScript中的内存管理是自动执行的,当遇到这种情况时,js引擎将会帮我们把这部分垃圾清除掉,这也就是我们常说的垃圾回收。

  • 那什么会被认为是垃圾呢?一般来讲当一个对象不在被GC根引用时就会被清除。GC根可以是浏览器的window,也可以是nodejs中的global。
    在这里插入图片描述

  • 但是垃圾回收不是万能的,如果我们写的逻辑不合理,导致一些并不会被使用的对象,一直被根对象引用,这是啥就会引起内存泄露。

  • 一旦超过浏览器的承受范围,浏览器就会崩溃白屏。
    我们敌人是 内存泄露
    最终的大boss是 一直拿着垃圾不让它被回收的坏人!

Memory面板初探

  • 一般我们能很容易发现严重的内存泄露问题 毕竟都白屏了,但是很难定位到是哪里导致的内存泄露,往往一个白屏问题 会牵扯到多处内存泄露。
  • 我们要如何发现呢?我们的武器是?
    • Memory面板
      • Memory面板中毕竟常用的两个功能是Heap snapshot和Allocation instrumentation on timeline
我们先从Memory面板的Heap snapshot讲起
  • 我们先创建一个html,里面仅有一段js

    <!DOCTYPE />
    <html>
        <body>
            <script>
                function Fruit(name) {
                    this.name = name;
                }
    
                var apple = new Fruit('apple');
    
            </script>
        </body>
    </html>
    
  • 我们打开这个页面 进入memory面板

如何记录一次Heap snapshot ?

1. 打开控制台的Memory面板 选择Heap snapshot
在这里插入图片描述

  1. 点击开始按钮 开始记录
    上面的圆形按钮和下面的“Take snapshot”按钮都可以点击
    在这里插入图片描述

3. 查看记录结果
点击后 就可以看到我们当前的内容情况了
在这里插入图片描述

如何看数据?

但是问题来了这个要怎么看呢,我们来一点点说明

  • 首选我们要知道的是这个记录是当前所有的内存情况,并根据constructor分组,有一些是我们无法直接调用的比如“(system)”或者“(compiled code)”,当然也有一些可以直接访问,我们比较熟悉的如“Math” “String”等。
  • 我们可以通过上方的搜索按钮,找到我们的类名Fruit,或者更直接下拉查看。
    在这里插入图片描述
  • 我们先看上半部分
    • 第一列Constructor:
      • 就像我之前所说的那样,对象会根据constructor分组
      • 点开分组后能看到当前类下的所有对象
    • 第二列 Distance
      • 与GC根的距离
    • 第三列 Shallow size
      • 对象本身占用内存的大小,不包含其引用的对象
      • 由其成员变量的数量和类型决定
    • 第四列 Retained size
      • 被GC回收的大小
      • 对象及其依赖对象的内存大小
  • 再来看下半部分
    • 下半部分展示了该对象都被哪些对象引用
      • 比如我们可以看到Fruit(apple)被window上的apple所引用
        在这里插入图片描述
如何对比?

当前,只有一次记录是无法很好的帮助我们定位内存泄露问题的,Memoery面板提供了对比功能,这样我们可以很方便的对比两次内存的记录

  • 比如我们把之前的代码稍微修改一下,增加个按钮
<!DOCTYPE />
<html>
    <body>
        <button id="btn">add fruit</button>
        <script>
            const fruits = [];
            const FRUIT_NAME = ['apple', 'orange', 'banana', 'peach', 'grape'];
            const addBtn = document.getElementById('btn');

            function Fruit(name) {
                this.name = name;
            }

            function getRandomFruit() {
                const random = Math.floor(Math.random() * FRUIT_NAME.length);
                const fruitName =  FRUIT_NAME[random];
                return new Fruit(fruitName);
            }

            addBtn.addEventListener('click', () => {
                const fruit = getRandomFruit();
                fruits.push(fruit)
            });
        </script>
    </body>
</html>
  • 打开页面后,我们记录一次,点击几次按钮后再记录一次,
  • 这样我们在右侧或得了两条记录, 当我们想要查看者两条记录的差异时可以
    • 右侧点击第二次记录
    • 上方左侧选择对比Comparison
    • 上方右侧选择第一次记录
    • 这样我们可以清晰的在面板中,Delta一列中看到Fruit增加的个数
      在这里插入图片描述
Memory面板的Allocation instrumentation on timeline
  • Memory面板除了可以记录当前的一个内存情况,也可以动态记录内存

  • 这样点击右侧的Profiles中,回到选择记录方式的页面
    在这里插入图片描述

  • 和上面一样 点击右侧圆形按钮或者点击下方的“Start”按钮都可以开始记录,

  • 点击按钮开始记录后,我们再点几次按钮,可以看到上面有很多柱形,随着按钮的点击而变化

  • 按右侧红色圆形按钮,结束记录
    在这里插入图片描述

    我们来解释下这个小条的含义,
    柱形的总高度代表当时代码执行时所分配的所有内存
    柱形灰色部分,代表已经被回收释放的内存
    柱形蓝色部分,代表还依然被占用的内容

  • 鼠标点击对应柱形,可以在下方看到和Take Snapshot中一样的内存分配情况
    在这里插入图片描述

    • 我们可以看到,此时Fruit被fruits数组所引用

代码中常见的内存泄漏(vue为例)

1. vue中beforeDestroy 生命周期单词写错

- beforeDestroy 和 destroy 单词写错
	```
	beforeDestory  错
	beforeDestroy  对
	```

2. EventListener 事件

- 忘写removeEventListener 事件
- addEventListener事件参数不匹配
- 参数不要写箭头函数
```
window.addEventListener('resize', this.resetHeight, true); 


window.removeEventListener('resize', this.resetHeight);   //错

window.removeEventListener('resize', this.resetHeight, true); //对


document.addEventListener('mousedown', e => {       //错
    const clientY = e.clientY;
    if (clientY > 0 && clientY < 70) {
        dragWindowHandle(true);
    }
});
```

3. 节流防抖

- 使用节流防抖时,注意添加和移除事件的参数,引用保持一致
```
window.addEventListener('resize', Utils.debounce(this.screenResizeHandle, 200), true);  //错

this.screenResizeHandle = Utils.debounce(this.screenResizeHandle, 200);
window.addEventListener('resize', this.screenResizeHandle, true);         //对
    
    
window.removeEventListener('resize', this.screenResizeHandle, true); 

```

4. 记得清除定时等引用变量 有可能造成闭包

```
 timer = setTimeout(() => {
            this.flag = true;
 }, 1000);
 beforeDestroy() {
    clearTimeout(timer);
}
```

5.发布订阅 $on 以后记得 $off

```
created() {
    EventBus.$on(EventTypes.SET_DOWNLOAD_FILES, this.getFileList);
},
beforeDestroy() {
    EventBus.$off(EventTypes.SET_DOWNLOAD_FILES, this.getFileList);
}
```

6.记得卸载第三方引用

created() {
        window.Offline.on('down', this.offlineUpload);
        window.Offline.on('up', this.onlineUpload);
    },
    beforeDestroy() {
        window.Offline.off('down', this.offlineUpload);
        window.Offline.off('up', this.onlineUpload);
    }
};

后语

好文章要分享给大家,转载 / 作者: 狐狸鹿妹子 百度

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值