VueJS SSR 后端绘制内存泄漏的相关解决经验

引言

Memory Leak 是最难排查调试的 Bug 种类之一,由于内存泄漏是个 undecidable problem,只有开发者才能明确一块内存是否是须要被回收。再加上内存泄漏也没有特定的报错信息,只能经过必定时间段的日志来判断是否存在内存泄漏。你们熟悉的经常使用调试工具对排查内存泄漏也没有用武之地。固然了,除了专门用于排查内存泄漏的工具(抓取Heap之类的工具)以外。javascript

对于不一样的语言,各类排查内存泄漏的方式方法也不尽相同。对于 JavaScript 来讲,针对不一样的平台,调试工具也是不同的,最经常使用的恐怕仍是 Chrome 自带的各类利器(针对 browser 也好,nodeJS 也好)都有不错的使用体验,网上也有不少使用教程。前端

此次我想给你们介绍的内存泄漏的定位方法,并不是工具的使用。而是一些经验的总结,也就是我所知道的 VueJS SSR 中最容易出现内存泄漏的地方,若是你们知道更多 VueJS SSR 内存泄漏点,能够在评论处留言告诉更多的人。vue

难点

遇到过 VueJS SSR 内存泄漏的朋友可能知道,针对 VueJS SSR 内存泄漏的排查,与普通 NodeJS 和 Browser 平台相比是要麻烦不少的。若是你使用了 webpack-dev-server 在本地调试,你会发现经常使用的内存泄漏工具毫无用武之地,由于抓取到的信息不只包括 VueJS SSR 进程信息,还包含了 Webpack 的进程信息,甚至还有 webpack-dev-server 的各类堆信息。固然了,你也能够经过各类手段来过滤掉无关的信息,从而只剩下 VueJS SSR 的堆信息。java

我在排查咱们组项目内存泄漏的时候,动用了各类常规工具,但最终发现 VueJS SSR 的内存泄漏有很大可能性出如今如下地方,也就说若是,你碰巧也有 VueJS SSR 内存泄漏的问题,先不要使用内存泄漏排查工具,首先从下面几个地方着手,看看是否有内存泄漏的逻辑。可能直击要害,节约时间。node

可能形成泄漏的位置

生命周期处的 beforeCreate/created

如下是 VueJS 开发者看过无数次的说明图,我还请你们再多看一遍webpack

在官方文档里,有这么一句话:git

Since there are no dynamic updates, of all the lifecycle hooks, only beforeCreate and created will be called during SSR. This means any code inside other lifecycle hooks such as beforeMount or mounted will only be executed on the client.github

也就是说 SSR 跟前端绘制同样,也有生命周期,只不过 SSR 的生命周期里只有 beforeCreate 和 created 。web

因此你须要首先排查你的组件的 beforeCreate 和 created 里面是否有内存泄漏的代码,或者他们是否调用了会内存泄漏的代码。后端

路由守卫(Route Guards)处

路由也是会引发 SSR 内存泄漏的地方之一

跟生命周期不一样,全部的 route guard 都会在 SSR 运行。他们分别都是

  • beforeEach
  • beforeRouteUpdate
  • beforeEnter
  • beforeRouteEnter
  • beforeResolve
  • afterEach
  • beforeRouteEnter

Data-Prefetch 处

还须要特别注意的地方就是 Date-prefetch 的地方,里面很容易出现内存泄漏的代码。 所谓 Date-prefetch 就是自定义实现的,在SSR处提早获取第三方数据,用于绘制的过程。

Global Mixin 处

这个内存泄漏的点想必你们都已经熟知,做者也在github上详细阐述过:GitHub issue

简单来讲,就是 global mixin 会给每一个 Vue 实例一个拷贝,而不是引用。

内存泄漏的例子

以上列举了一些可能出现内存泄漏的地方,那么具体怎么样的代码才会引发内存泄漏呢?引发代码泄漏的例子网上有不少,我在这里想给你们介绍几种常见的泄漏例子。

不当心形成的全局变量

function foo(arg) {
    bar = "this is a hidden global variable";
}
复制代码

以上的代码会顺利运行,可是由于不当心声明了一个 bar 的变量。至关于:

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
复制代码

生成了一个全局变量 window.bar

若是不手动回收,这个全局变量会一直存在于内存中,不会被CG回收。聚沙成塔,最后形成内存泄漏。

如今你们都是在各类模块化(CommonJS/AMD/CMD/etc..)以后的环境下进行开发,这种全局变量的内存泄漏的问题基本上是被消除了。可是要提醒你们,因为JavaScript的各类特性,会有不少意想不到的情况发生。当摸不清头脑的时候,能够尝试从这些特性出发找到问题。

被遗忘了的 Timer 或者 callback

请你们先看如下的例子

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // Do stuff with node and someResource.
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);
复制代码

乍一看没啥问题,以后若是 Node 节点从DOM上被移除,由于上面的 callback 对 Node 节点有引用,因此 Node 节点会一直常驻内存,不会被CG回收。

要避免以上问题,就要养成 removeEventListenerclearInterval 的习惯。

var someResource = getData();
var interval = setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // Do stuff with node and someResource.
        node.innerHTML = JSON.stringify(someResource));
    } else {
        // Remove Timer
        clearInterval(interval);
    }
}, 1000);
复制代码

还好比:

var element = document.getElementById('button');

function onClick(event) {
    element.innerHtml = 'text';
}

element.addEventListener('click', onClick);
// Do stuff
element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);
// Now when element goes out of scope,
// both element and onClick will be collected even in old browsers that don't
// handle cycles well.
复制代码

addEventListener 以后已经要记得 removeEventListener

闭包

闭包形成内存泄漏的状况比较复杂,并且较难查找。限于本文主旨,不作原理说明。

可是,在这里我给你们推荐一篇很是不错的文章,详细地介绍了闭包是如何形成内存泄漏的过程:An interesting kind of JavaScript memory leak

总结

我的认为 VueJS SSR 后端绘制内存泄漏形成影响要比普通的 VueJS 前端内存泄漏形成的影响要更大。

前端内存泄漏的影响,都是发生在客户机器上,并且基本上现代浏览器也会作好保护机制,通常自行刷新以后都会解决。可是,一旦后端绘制内存泄漏形成宕机以后,整个服务器都会受影响,危险性更大,搞很差年终奖就没了。

前端工程师通常都是关注于浏览器端表现,在开发过程当中的内存泄漏问题不太在乎也不太容易被发现。通常都是在项目上线一段时间以后,才发现内存泄漏的状况。那个时候再去着手,可能会有些无从下手或者手忙脚乱。

那么,就让咱们在开发的时候开始关注内存泄漏问题,将 VueJS SSR 后端绘制内存泄漏问题扼杀于襁褓之中。

笔者博客

相关文章
相关标签/搜索